File manager - Edit - /home/rangceb/diohome.com/wp-includes6790ed/fonts/components.tar
Back
CompatibilityWarning.php 0000644 00000210763 15222651263 0011431 0 ustar 00 <?php /** * ET_Core_CompatibilityWarning class file. * * @class ET_Core_CompatibilityWarning * @package Core */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } if ( ! class_exists( 'ET_Core_CompatibilityWarning' ) ) : /** * Plugin & theme compatibility warning system backported from WP 5.5. * * This system is only intended for users who use Divi theme with WP 5.4.2 below and Divi * Builder plugin with WP 5.2 below. There are some areas where it overrides or modifies * the themes & plugins management template to show warnings when the current WP and/or * PHP versions doesn't work with the current and/or upcoming ET plugins/themes. * * A. Update Core * 1. Plugins list section. * 2. Themes list section. * * B. Manage Themes * On each of incompatible themes, shows warning and disables Activate & Live Preview * buttons when the theme is not activated yet. * 1. Themes List (tmpl-theme). * 2. Theme Details (tmpl-theme-single). * * C. Theme Customizer * On each of incompatible themes, shows warning and disables Live Preview button when * the theme is not activated yet. In addition, disable Publish button for the current * active theme. * 1. Themes List (tmpl-theme). * 2. Theme Details (tmpl-theme-single). * 3. Publish Button for current active theme. * * D. Plugins List * 1. Plugins List via `after_plugin_row_` action hook. * 2. Plugin activation warning. ET plugins that want to use this warning should * register `maybe_deactivate_incompatible_plugin()` on their activation hook. * * @since 4.7.0 */ class ET_Core_CompatibilityWarning { /** * Class instance. * * @var ET_Core_CompatibilityWarning */ public static $instance_class; /** * WP version where the official warning system is introduced. * * @var string */ public $supported_wp_version = array( 'plugin' => '5.3.0', 'theme' => '5.5.0', ); /** * Class constructor. */ public function __construct() { global $wp_version; // Ensure the system is loaded on lower version of supported version. if ( version_compare( $wp_version, $this->supported_wp_version[ ET_CORE_TYPE ], '>=' ) ) { return; } add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); // A. Update Core - Overrides plugins and themes updates table body. add_action( 'admin_print_footer_scripts-update-core.php', array( $this, 'overrides_update_core_plugins_table_body' ) ); add_action( 'admin_print_footer_scripts-update-core.php', array( $this, 'overrides_update_core_themes_table_body' ) ); // B. Manage Themes - Overrides themes list & details templates. add_filter( 'wp_prepare_themes_for_js', array( $this, 'set_theme_additional_properties' ) ); add_action( 'admin_print_footer_scripts-themes.php', array( $this, 'overrides_tmpl_theme' ) ); // C. Theme Customizer - Overrides themes list & details templates. add_action( 'customize_controls_print_footer_scripts', array( $this, 'overrides_tmpl_customize_control_theme_content' ) ); // D. Plugins - Overrides current plugin list. Default priority is 20. add_action( 'load-plugins.php', array( $this, 'overrides_plugins_table_rows' ), 21 ); } /** * Returns instance of the class. * * @since 4.7.0 * * @return ET_Core_CompatibilityWarning */ public static function instance() { if ( ! isset( self::$instance_class ) ) { self::$instance_class = new self(); } return self::$instance_class; } /** * Set theme additional properties before it's rendered on Manage Themes page. * * @since 4.7.0 * * @param array $prepared_themes List of available themes. * * @return array */ public function set_theme_additional_properties( $prepared_themes ) { // Bail early if the $prepared_themes is empty. if ( empty( $prepared_themes ) ) { return $prepared_themes; } // 1. Get available themes update. $theme_updates = array(); if ( current_user_can( 'update_themes' ) ) { $updates_themes_transient = get_site_transient( 'update_themes' ); if ( isset( $updates_themes_transient->response ) ) { $theme_updates = $updates_themes_transient->response; } } // 2. Assign compatibility properties. $themes = $prepared_themes; foreach ( $themes as $theme_slug => $theme_info ) { // Ensure style.css file exist. $theme_root = get_theme_root( $theme_slug ); $theme_file = "{$theme_root}/{$theme_slug}/style.css"; if ( ! file_exists( $theme_file ) ) { continue; } // Get WP & PHP compatibility info. $theme_headers = get_file_data( $theme_file, array( 'RequiresWP' => 'Requires at least', 'RequiresPHP' => 'Requires PHP', ), 'theme' ); $require_wp = et_()->array_get( $theme_headers, 'RequiresWP', null ); $require_php = et_()->array_get( $theme_headers, 'RequiresPHP', null ); $update_requires_wp = et_()->array_get( $theme_updates, array( $theme_slug, 'requires' ), null ); $update_requires_php = et_()->array_get( $theme_updates, array( $theme_slug, 'requires_php' ), null ); $compatibility_properties = array( 'compatibleWP' => is_wp_version_compatible( $require_wp ), 'compatiblePHP' => is_php_version_compatible( $require_php ), 'updateResponse' => array( 'compatibleWP' => is_wp_version_compatible( $update_requires_wp ), 'compatiblePHP' => is_php_version_compatible( $update_requires_php ), ), ); $prepared_themes[ $theme_slug ] = array_merge( $prepared_themes[ $theme_slug ], $compatibility_properties ); } return $prepared_themes; } /** * Get plugins data for Update Core page. * * The data processing is backported from WP 5.5 with few modification. * * @see {list_plugin_updates()} of WP 5.5 * * @since 4.7.0 * * @return array */ public function get_update_core_plugins_data() { $plugin_updates = get_plugin_updates(); $plugin_processed = array(); // Bail early if there is no plugin updates. if ( empty( $plugin_updates ) ) { return array(); } foreach ( $plugin_updates as $plugin_file => $plugin_data ) { // reason: The properties come from WP plugin data. // phpcs:disable ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase $plugin_name = $plugin_data->Name; $plugin_version = $plugin_data->Version; // phpcs:enable // a. Get current and update WP version. $wp_version = get_bloginfo( 'version' ); $cur_wp_version = preg_replace( '/-.*$/', '', $wp_version ); $core_updates = get_core_updates(); if ( ! isset( $core_updates[0]->response ) || 'latest' === $core_updates[0]->response || 'development' === $core_updates[0]->response || version_compare( $core_updates[0]->current, $cur_wp_version, '=' ) ) { $core_update_version = false; } else { $core_update_version = $core_updates[0]->current; } // b. Check PHP versions compatibility. WP doesn't check WP versions compatibility. $requires_php = isset( $plugin_data->update->requires_php ) ? $plugin_data->update->requires_php : null; $compatible_php = is_php_version_compatible( $requires_php ); // c. Icon. $icon = '<span class="dashicons dashicons-admin-plugins"></span>'; $preferred_icons = array( 'svg', '2x', '1x', 'default' ); foreach ( $preferred_icons as $preferred_icon ) { if ( ! empty( $plugin_data->update->icons[ $preferred_icon ] ) ) { $icon = '<img src="' . esc_url( $plugin_data->update->icons[ $preferred_icon ] ) . '" alt="" />'; break; } } // d. Process compatibility warning text. // Get plugin compat for running version of WordPress. if ( isset( $plugin_data->update->tested ) && version_compare( $plugin_data->update->tested, $cur_wp_version, '>=' ) ) { /* translators: %s: WordPress version. */ $compat = '<br />' . sprintf( __( 'Compatibility with WordPress %s: 100%% (according to its author)' ), $cur_wp_version ); } else { /* translators: %s: WordPress version. */ $compat = '<br />' . sprintf( __( 'Compatibility with WordPress %s: Unknown' ), $cur_wp_version ); } // Get plugin compat for updated version of WordPress. if ( $core_update_version ) { if ( isset( $plugin_data->update->tested ) && version_compare( $plugin_data->update->tested, $core_update_version, '>=' ) ) { /* translators: %s: WordPress version. */ $compat .= '<br />' . sprintf( __( 'Compatibility with WordPress %s: 100%% (according to its author)' ), $core_update_version ); } else { /* translators: %s: WordPress version. */ $compat .= '<br />' . sprintf( __( 'Compatibility with WordPress %s: Unknown' ), $core_update_version ); } } // Get plugin compat for updated version of PHP. if ( ! $compatible_php && current_user_can( 'update_php' ) ) { $compat .= '<br>' . __( 'This update doesn’t work with your version of PHP.' ) . ' '; $compat .= sprintf( /* translators: %s: URL to Update PHP page. */ __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); $annotation = wp_get_update_php_annotation(); if ( $annotation ) { $compat .= '</p><p><em>' . $annotation . '</em>'; } } // Get the upgrade notice for the new plugin version. if ( isset( $plugin_data->update->upgrade_notice ) ) { $upgrade_notice = '<br />' . wp_strip_all_tags( $plugin_data->update->upgrade_notice ); } else { $upgrade_notice = ''; } $details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data->update->slug . '§ion=changelog&TB_iframe=true&width=640&height=662' ); $details = sprintf( '<a href="%1$s" class="thickbox open-plugin-details-modal" aria-label="%2$s">%3$s</a>', esc_url( $details_url ), /* translators: 1: Plugin name, 2: Version number. */ esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $plugin_data->update->new_version ) ), /* translators: %s: Plugin version. */ sprintf( __( 'View version %s details.' ), $plugin_data->update->new_version ) ); $checkbox_id = 'checkbox_' . md5( $plugin_name ); // Plugin template properties. There is no compatible_wp property passed here. $plugin_processed[ $plugin_file ] = array( 'plugin_file' => esc_attr( $plugin_file ), 'name' => esc_attr( $plugin_name ), 'checkbox_id' => esc_attr( 'checkbox_' . md5( $plugin_name ) ), 'icon' => et_core_intentionally_unescaped( $icon, 'html' ), 'version' => esc_attr( $plugin_version ), 'new_version' => esc_attr( $plugin_data->update->new_version ), 'compatible_php' => $compatible_php, 'compat' => et_core_intentionally_unescaped( $compat, 'html' ), 'upgrade_notice' => et_core_intentionally_unescaped( $upgrade_notice, 'html' ), 'details' => et_core_intentionally_unescaped( $details, 'html' ), ); } return $plugin_processed; } /** * Get themes data for Update Core page. * * @since 4.7.0 * * @return array */ public function get_update_core_themes_data() { $theme_updates = get_theme_updates(); $theme_processed = array(); // Bail early if there is no theme updates. if ( empty( $theme_updates ) ) { return array(); } foreach ( $theme_updates as $stylesheet => $theme ) { // a. Check compatibility. $requires_wp = et_()->array_get( $theme->update, 'requires', null ); $requires_php = et_()->array_get( $theme->update, 'requires_php', null ); $compatible_wp = is_wp_version_compatible( $requires_wp ); $compatible_php = is_php_version_compatible( $requires_php ); // b. Process compatibility warning text. $compat = ''; if ( ! $compatible_wp && ! $compatible_php ) { $compat .= '<br>' . __( 'This update doesn’t work with your versions of WordPress and PHP.' ) . ' '; if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { $compat .= sprintf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); $annotation = wp_get_update_php_annotation(); if ( $annotation ) { $compat .= '</p><p><em>' . $annotation . '</em>'; } } elseif ( current_user_can( 'update_core' ) ) { $compat .= sprintf( /* translators: %s: URL to WordPress Updates screen. */ __( '<a href="%s">Please update WordPress</a>.' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { $compat .= sprintf( /* translators: %s: URL to Update PHP page. */ __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); $annotation = wp_get_update_php_annotation(); if ( $annotation ) { $compat .= '</p><p><em>' . $annotation . '</em>'; } } } elseif ( ! $compatible_wp ) { $compat .= '<br>' . __( 'This update doesn’t work with your version of WordPress.' ) . ' '; if ( current_user_can( 'update_core' ) ) { $compat .= sprintf( /* translators: %s: URL to WordPress Updates screen. */ __( '<a href="%s">Please update WordPress</a>.' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } } elseif ( ! $compatible_php ) { $compat .= '<br>' . __( 'This update doesn’t work with your version of PHP.' ) . ' '; if ( current_user_can( 'update_php' ) ) { $compat .= sprintf( /* translators: %s: URL to Update PHP page. */ __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); $annotation = wp_get_update_php_annotation(); if ( $annotation ) { $compat .= '</p><p><em>' . $annotation . '</em>'; } } } // Theme template properties. $theme_processed[ $stylesheet ] = array( 'stylesheet' => esc_attr( $stylesheet ), 'name' => esc_attr( $theme->display( 'Name' ) ), 'checkbox_id' => esc_attr( 'checkbox_' . md5( $theme->get( 'Name' ) ) ), 'screenshot' => esc_url( $theme->get_screenshot() ), 'version' => esc_attr( $theme->display( 'Version' ) ), 'new_version' => esc_attr( et_()->array_get( $theme->update, 'new_version', '' ) ), 'compatible_wp' => $compatible_wp, 'compatible_php' => $compatible_php, 'compat' => et_core_esc_previously( $compat ), ); } return $theme_processed; } /** * Enqueue compatibility warning scripts and its local data. * * @since 4.7.0 */ public function enqueue_scripts() { global $pagenow, $wp_customize; // Bail early if the current page is not one of the allowed pages. $allowed_pages = array( 'update-core.php', 'customize.php', 'themes.php', ); if ( ! in_array( $pagenow, $allowed_pages, true ) ) { return; } // Enqueue main scripts. wp_enqueue_script( 'et_compatibility_warning_script', ET_CORE_URL . 'admin/js/compatibility-warning.js', array( 'jquery' ), ET_CORE_VERSION, true ); $compatibility_warning = array(); if ( 'update-core.php' === $pagenow ) { $compatibility_warning['update_core_data'] = array( 'plugins' => self::get_update_core_plugins_data(), 'themes' => self::get_update_core_themes_data(), ); } elseif ( 'themes.php' === $pagenow ) { $compatibility_warning['manage_themes_data'] = true; } elseif ( 'customize.php' === $pagenow ) { // Ensure style.css file exist. $theme_root = $wp_customize->theme()->theme_root; $theme_slug = $wp_customize->theme()->stylesheet; $theme_file = "{$theme_root}/{$theme_slug}/style.css"; // Get WP & PHP compatibility info. $theme_headers = array(); if ( file_exists( $theme_file ) ) { $theme_headers = get_file_data( $theme_file, array( 'RequiresWP' => 'Requires at least', 'RequiresPHP' => 'Requires PHP', ), 'theme' ); } $requires_wp = et_()->array_get( $theme_headers, 'RequiresWP', false ); $requires_php = et_()->array_get( $theme_headers, 'RequiresPHP', false ); // Theme Customizer - Used for disable publish button. $compatibility_warning['customizer_data'] = array( 'compatible_wp' => is_wp_version_compatible( $requires_wp ), 'compatible_php' => is_php_version_compatible( $requires_php ), 'disabled_text' => esc_html_x( 'Cannot Activate', 'theme' ), ); } wp_localize_script( 'et_compatibility_warning_script', 'et_compatibility_warning', $compatibility_warning ); } /** * Overrides table body of plugin updates section. * * The structure is backported from WP 5.5 without any modification. * * @see {list_plugin_updates()} of WP 5.5 * * @since 4.7.0 */ public function overrides_update_core_plugins_table_body() { // Bail early if there is no plugin updates. if ( empty( get_plugin_updates() ) ) { return; } ?> <script type="text/html" id="tmpl-et-update-core-plugins-table-body"> <# if (data.plugins) { #> <# _.each(data.plugins, function(plugin, slug) { #> <tr> <td class="check-column"> <# if (plugin.compatible_php) { #> <input type="checkbox" name="checked[]" id="{{plugin.checkbox_id}}" value="{{plugin.plugin_file}}" /> <label for="{{plugin.checkbox_id}}" class="screen-reader-text"> <?php /* translators: %s: Plugin name. */ printf( esc_html__( 'Select %s' ), '{{plugin.name}}' ); ?> </label> <# } #> </td> <td class="plugin-title"><p> {{{plugin.icon}}} <strong>{{plugin.name}}</strong> <?php printf( /* translators: 1: Plugin version, 2: New version. */ esc_html__( 'You have version %1$s installed. Update to %2$s.' ), '{{plugin.version}}', '{{plugin.new_version}}' ); echo ' {{{plugin.details}}}{{{plugin.compat}}}{{{plugin.upgrade_notice}}}'; ?> </p></td> </tr> <# }); #> <# } #> </script> <?php } /** * Overrides table body of theme updates section. * * The structure is backported from WP 5.5 without any modification. * * @see {list_theme_updates()} of WP 5.5 * * @since 4.7.0 */ public function overrides_update_core_themes_table_body() { // Bail early if there is no theme updates. if ( empty( get_theme_updates() ) ) { return; } ?> <script type="text/html" id="tmpl-et-update-core-themes-table-body"> <# if (data.themes) { #> <# _.each(data.themes, function(theme, slug) { #> <tr> <td class="check-column"> <# if (theme.compatible_wp && theme.compatible_php) { #> <input type="checkbox" name="checked[]" id="{{theme.checkbox_id}}" value="{{theme.styelsheet}}" /> <label for="{{theme.checkbox_id}}" class="screen-reader-text"> <?php /* translators: %s: Theme name. */ printf( esc_html__( 'Select %s' ), '{{theme.name}}' ); ?> </label> <# } #> </td> <td class="plugin-title"><p> <img src="{{theme.screenshot}}" width="85" height="64" class="updates-table-screenshot" alt="" /> <strong>{{theme.name}}</strong> <?php printf( /* translators: 1: Theme version, 2: New version. */ esc_html__( 'You have version %1$s installed. Update to %2$s.' ), '{{theme.version}}', '{{theme.new_version}}' ); echo ' {{{theme.compat}}}'; ?> </p></td> </tr> <# }); #> <# } #> </script> <?php } /** * Overrides theme info & details display of Manage Themes. * * The structure is backported from WP 5.5 without any modification. * * @see {wp-admin/themes.php} of WP 5.5 * * @since 4.7.0 */ public function overrides_tmpl_theme() { ?> <script id="tmpl-theme" type="text/template"> <# if ( data.screenshot[0] ) { #> <div class="theme-screenshot"> <img src="{{ data.screenshot[0] }}" alt="" /> </div> <# } else { #> <div class="theme-screenshot blank"></div> <# } #> <# if ( data.hasUpdate ) { #> <# if ( data.updateResponse.compatibleWP && data.updateResponse.compatiblePHP ) { #> <div class="update-message notice inline notice-warning notice-alt"><p> <# if ( data.hasPackage ) { #> <?php echo et_core_intentionally_unescaped( __( 'New version available. <button class="button-link" type="button">Update now</button>' ), 'html' ); ?> <# } else { #> <?php esc_html_e( 'New version available.' ); ?> <# } #> </p></div> <# } else { #> <div class="update-message notice inline notice-error notice-alt"><p> <# if ( ! data.updateResponse.compatibleWP && ! data.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your versions of WordPress and PHP.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.updateResponse.compatibleWP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of WordPress.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of PHP.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p></div> <# } #> <# } #> <# if ( ! data.compatibleWP || ! data.compatiblePHP ) { #> <div class="notice notice-error notice-alt"><p> <# if ( ! data.compatibleWP && ! data.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your versions of WordPress and PHP.' ), 'html' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.compatibleWP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of WordPress.' ), 'html' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of PHP.' ), 'html' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p></div> <# } #> <span class="more-details" id="{{ data.id }}-action"><?php esc_html_e( 'Theme Details' ); ?></span> <div class="theme-author"> <?php /* translators: %s: Theme author name. */ printf( esc_html__( 'By %s' ), '{{{ data.author }}}' ); ?> </div> <div class="theme-id-container"> <# if ( data.active ) { #> <h2 class="theme-name" id="{{ data.id }}-name"> <span><?php echo esc_html_x( 'Active:', 'theme' ); ?></span> {{{ data.name }}} </h2> <# } else { #> <h2 class="theme-name" id="{{ data.id }}-name">{{{ data.name }}}</h2> <# } #> <div class="theme-actions"> <# if ( data.active ) { #> <# if ( data.actions.customize ) { #> <a class="button button-primary customize load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php esc_html_e( 'Customize' ); ?></a> <# } #> <# } else { #> <# if ( data.compatibleWP && data.compatiblePHP ) { #> <?php /* translators: %s: Theme name. */ $aria_label = sprintf( esc_html_x( 'Activate %s', 'theme' ), '{{ data.name }}' ); ?> <a class="button activate" href="{{{ data.actions.activate }}}" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php esc_html_e( 'Activate' ); ?></a> <a class="button button-primary load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php esc_html_e( 'Live Preview' ); ?></a> <# } else { #> <?php /* translators: %s: Theme name. */ $aria_label = sprintf( esc_html_x( 'Cannot Activate %s', 'theme' ), '{{ data.name }}' ); ?> <a class="button disabled" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php echo esc_html_x( 'Cannot Activate', 'theme' ); ?></a> <a class="button button-primary hide-if-no-customize disabled"><?php esc_html_e( 'Live Preview' ); ?></a> <# } #> <# } #> </div> </div> </script> <script id="tmpl-theme-single" type="text/template"> <div class="theme-backdrop"></div> <div class="theme-wrap wp-clearfix" role="document"> <div class="theme-header"> <button class="left dashicons dashicons-no"><span class="screen-reader-text"><?php esc_html_e( 'Show previous theme' ); ?></span></button> <button class="right dashicons dashicons-no"><span class="screen-reader-text"><?php esc_html_e( 'Show next theme' ); ?></span></button> <button class="close dashicons dashicons-no"><span class="screen-reader-text"><?php esc_html_e( 'Close details dialog' ); ?></span></button> </div> <div class="theme-about wp-clearfix"> <div class="theme-screenshots"> <# if ( data.screenshot[0] ) { #> <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> <# } else { #> <div class="screenshot blank"></div> <# } #> </div> <div class="theme-info"> <# if ( data.active ) { #> <span class="current-label"><?php esc_html_e( 'Current Theme' ); ?></span> <# } #> <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"> <?php /* translators: %s: Theme version. */ printf( esc_html__( 'Version: %s' ), '{{ data.version }}' ); ?> </span></h2> <p class="theme-author"> <?php /* translators: %s: Theme author link. */ printf( esc_html__( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?> </p> <# if ( ! data.compatibleWP || ! data.compatiblePHP ) { #> <div class="notice notice-error notice-alt notice-large"><p> <# if ( ! data.compatibleWP && ! data.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your versions of WordPress and PHP.' ), 'html' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.compatibleWP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of WordPress.' ), 'html' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of PHP.' ), 'html' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p></div> <# } #> <# if ( data.hasUpdate ) { #> <# if ( data.updateResponse.compatibleWP && data.updateResponse.compatiblePHP ) { #> <div class="notice notice-warning notice-alt notice-large"> <h3 class="notice-title"><?php esc_html_e( 'Update Available' ); ?></h3> {{{ data.update }}} </div> <# } else { #> <div class="notice notice-error notice-alt notice-large"> <h3 class="notice-title"><?php esc_html_e( 'Update Incompatible' ); ?></h3> <p> <# if ( ! data.updateResponse.compatibleWP && ! data.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your versions of WordPress and PHP.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.updateResponse.compatibleWP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of WordPress.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of PHP.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p> </div> <# } #> <# } #> <p class="theme-description">{{{ data.description }}}</p> <# if ( data.parent ) { #> <p class="parent-theme"> <?php /* translators: %s: Theme name. */ printf( esc_html__( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?> </p> <# } #> <# if ( data.tags ) { #> <p class="theme-tags"><span><?php esc_html_e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> <# } #> </div> </div> <div class="theme-actions"> <div class="active-theme"> <a href="{{{ data.actions.customize }}}" class="button button-primary customize load-customize hide-if-no-customize"><?php esc_html_e( 'Customize' ); ?></a> <?php echo et_core_intentionally_unescaped( implode( ' ', $GLOBALS['current_theme_actions'] ), 'html' ); ?> </div> <div class="inactive-theme"> <# if ( data.compatibleWP && data.compatiblePHP ) { #> <?php /* translators: %s: Theme name. */ $aria_label = sprintf( esc_html_x( 'Activate %s', 'theme' ), '{{ data.name }}' ); ?> <# if ( data.actions.activate ) { #> <a href="{{{ data.actions.activate }}}" class="button activate" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php esc_html_e( 'Activate' ); ?></a> <# } #> <a href="{{{ data.actions.customize }}}" class="button button-primary load-customize hide-if-no-customize"><?php esc_html_e( 'Live Preview' ); ?></a> <# } else { #> <?php /* translators: %s: Theme name. */ $aria_label = sprintf( esc_html_x( 'Cannot Activate %s', 'theme' ), '{{ data.name }}' ); ?> <# if ( data.actions.activate ) { #> <a class="button disabled" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php echo esc_html_x( 'Cannot Activate', 'theme' ); ?></a> <# } #> <a class="button button-primary hide-if-no-customize disabled"><?php esc_html_e( 'Live Preview' ); ?></a> <# } #> </div> <# if ( ! data.active && data.actions['delete'] ) { #> <a href="{{{ data.actions['delete'] }}}" class="button delete-theme"><?php esc_html_e( 'Delete' ); ?></a> <# } #> </div> </div> </script> <?php } /** * Overrides plugin warning display of Plugins list page. * * @see {wp_plugin_update_rows()} of WP 5.5 * * @since 4.7.0 */ public function overrides_plugins_table_rows() { if ( ! current_user_can( 'update_plugins' ) ) { return; } $update_plugins = get_site_transient( 'update_plugins' ); if ( isset( $update_plugins->response ) && is_array( $update_plugins->response ) ) { foreach ( $update_plugins->response as $plugin_file => $plugin ) { $requires_php = isset( $plugin->requires_php ) ? $plugin->requires_php : null; $compatible_php = is_php_version_compatible( $requires_php ); // Bail early if the package empty or already compatible with current PHP version. if ( empty( $plugin->package ) || $compatible_php ) { continue; } // Need to remove default action before we can replace it with the new one. remove_action( "after_plugin_row_$plugin_file", 'wp_plugin_update_row', 10, 2 ); add_action( "after_plugin_row_$plugin_file", array( $this, 'plugin_update_row_compatibility_error' ), 10, 2 ); } } } /** * Display plugin update row with error compatibility. * * @see {wp_plugin_update_row()} of WP 5.5 * * @since 4.7.0 * * @param string $file Plugin basename. * @param array $plugin_data Plugin information. */ public function plugin_update_row_compatibility_error( $file, $plugin_data ) { if ( ! is_network_admin() && is_multisite() ) { return; } $update_plugins = get_site_transient( 'update_plugins' ); if ( ! isset( $update_plugins->response[ $file ] ) ) { return; } // a. Plugin response. $response = $update_plugins->response[ $file ]; // b. Plugin name. $plugins_allowedtags = array( 'a' => array( 'href' => array(), 'title' => array(), ), 'abbr' => array( 'title' => array() ), 'acronym' => array( 'title' => array() ), 'code' => array(), 'em' => array(), 'strong' => array(), ); $plugin_name = wp_kses( $plugin_data['Name'], $plugins_allowedtags ); // c. Details URL. $details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $response->slug . '§ion=changelog&TB_iframe=true&width=600&height=800' ); // d. Active class. if ( is_network_admin() ) { $active_class = is_plugin_active_for_network( $file ) ? ' active' : ''; } else { $active_class = is_plugin_active( $file ) ? ' active' : ''; } /** * Column count. * * @var WP_Plugins_List_Table $wp_list_table */ $wp_list_table = _get_list_table( 'WP_Plugins_List_Table', array( 'screen' => get_current_screen(), ) ); // f. Error text. $update_php_notation = wp_get_update_php_annotation(); $error_text = sprintf( /* translators: 1: Plugin name, 2: Details URL, 3: Additional link attributes, 4: Version number 5: URL to Update PHP page. */ __( 'There is a new version of %1$s available, but it doesn’t work with your version of PHP. <a href="%2$s" %3$s>View version %4$s details</a> or <a href="%5$s">learn more about updating PHP</a>. %6$s' ), $plugin_name, esc_url( $details_url ), sprintf( 'class="thickbox open-plugin-details-modal" aria-label="%s"', /* translators: 1: Plugin name, 2: Version number. */ esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) ) ), esc_attr( $response->new_version ), esc_url( wp_get_update_php_url() ), // #5 ! empty( $update_php_notation ) ? sprintf( __( '<br><em>%s</em>' ), $update_php_notation ) : '' ); printf( /* translators: 1: Active class, 2: Update slug, 3: Slug, 4: Plugin file, 5: Column count. */ '<tr class="plugin-update-tr%1$s" id="%2$s" data-slug="%3$s" data-plugin="%4$s"> <td colspan="%5$s" class="plugin-update colspanchange"> <div class="update-message notice inline notice-error notice-alt"> <p>%6$s</p> </div> </td> </tr>', esc_attr( $active_class ), esc_attr( $response->slug . '-update' ), esc_attr( $response->slug ), esc_attr( $file ), esc_attr( $wp_list_table->get_column_count() ), // #5 et_core_intentionally_unescaped( $error_text, 'html' ) ); } /** * Overrides theme info & details display of Theme Customizer. * * The structure is backported from WP 5.5 without any modification. * * @see {WP_Customize_Theme_Control::content_template()} of WP 5.5 * * @since 4.7.0 */ public function overrides_tmpl_customize_control_theme_content() { /* translators: %s: Theme name. */ $details_label = sprintf( esc_html__( 'Details for theme: %s' ), '{{ data.theme.name }}' ); /* translators: %s: Theme name. */ $customize_label = sprintf( esc_html__( 'Customize theme: %s' ), '{{ data.theme.name }}' ); /* translators: %s: Theme name. */ $preview_label = sprintf( esc_html__( 'Live preview theme: %s' ), '{{ data.theme.name }}' ); /* translators: %s: Theme name. */ $install_label = sprintf( esc_html__( 'Install and preview theme: %s' ), '{{ data.theme.name }}' ); ?> <script id="tmpl-customize-control-theme-content" type="text/html"> <# if ( data.theme.active ) { #> <div class="theme active" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action"> <# } else { #> <div class="theme" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action"> <# } #> <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> <div class="theme-screenshot"> <img data-src="{{ data.theme.screenshot[0] }}" alt="" /> </div> <# } else { #> <div class="theme-screenshot blank"></div> <# } #> <span class="more-details theme-details" id="{{ data.section }}-{{ data.theme.id }}-action" aria-label="<?php echo esc_attr( $details_label ); ?>"><?php esc_html_e( 'Theme Details' ); ?></span> <div class="theme-author"> <?php /* translators: Theme author name. */ printf( esc_html_x( 'By %s', 'theme author' ), '{{ data.theme.author }}' ); ?> </div> <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> <# if ( data.theme.updateResponse.compatibleWP && data.theme.updateResponse.compatiblePHP ) { #> <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"> <p> <?php if ( is_multisite() ) { esc_html_e( 'New version available.' ); } else { printf( /* translators: %s: "Update now" button. */ esc_html__( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . esc_html__( 'Update now' ) . '</button>' ); } ?> </p> </div> <# } else { #> <div class="update-message notice inline notice-error notice-alt" data-slug="{{ data.theme.id }}"> <p> <# if ( ! data.theme.updateResponse.compatibleWP && ! data.theme.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your versions of WordPress and PHP.' ), 'html' ), '{{{ data.theme.name }}}' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.theme.updateResponse.compatibleWP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of WordPress.' ), 'html' ), '{{{ data.theme.name }}}' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.theme.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of PHP.' ), 'html' ), '{{{ data.theme.name }}}' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p> </div> <# } #> <# } #> <# if ( ! data.theme.compatibleWP || ! data.theme.compatiblePHP ) { #> <div class="notice notice-error notice-alt"><p> <# if ( ! data.theme.compatibleWP && ! data.theme.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your versions of WordPress and PHP.' ), 'html' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.theme.compatibleWP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of WordPress.' ), 'html' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.theme.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of PHP.' ), 'html' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p></div> <# } #> <# if ( data.theme.active ) { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> <span><?php echo esc_html_x( 'Previewing:', 'theme' ); ?></span> {{ data.theme.name }} </h3> <div class="theme-actions"> <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php esc_html_e( 'Customize' ); ?></button> </div> </div> <div class="notice notice-success notice-alt"><p><?php echo esc_html_x( 'Installed', 'theme' ); ?></p></div> <# } else if ( 'installed' === data.theme.type ) { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> <div class="theme-actions"> <# if ( data.theme.compatibleWP && data.theme.compatiblePHP ) { #> <button type="button" class="button button-primary preview-theme" aria-label="<?php echo esc_attr( $preview_label ); ?>" data-slug="{{ data.theme.id }}"><?php esc_html_e( 'Live Preview' ); ?></button> <# } else { #> <button type="button" class="button button-primary disabled" aria-label="<?php echo esc_attr( $preview_label ); ?>"><?php esc_html_e( 'Live Preview' ); ?></button> <# } #> </div> </div> <div class="notice notice-success notice-alt"><p><?php echo esc_html_x( 'Installed', 'theme' ); ?></p></div> <# } else { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> <div class="theme-actions"> <# if ( data.theme.compatibleWP && data.theme.compatiblePHP ) { #> <button type="button" class="button button-primary theme-install preview" aria-label="<?php echo esc_attr( $install_label ); ?>" data-slug="{{ data.theme.id }}" data-name="{{ data.theme.name }}"><?php esc_html_e( 'Install & Preview' ); ?></button> <# } else { #> <button type="button" class="button button-primary disabled" aria-label="<?php echo esc_attr( $install_label ); ?>" disabled><?php esc_html_e( 'Install & Preview' ); ?></button> <# } #> </div> </div> <# } #> </div> </script> <script type="text/html" id="tmpl-customize-themes-details-view"> <div class="theme-backdrop"></div> <div class="theme-wrap wp-clearfix" role="document"> <div class="theme-header"> <button type="button" class="left dashicons dashicons-no"><span class="screen-reader-text"><?php esc_html_e( 'Show previous theme' ); ?></span></button> <button type="button" class="right dashicons dashicons-no"><span class="screen-reader-text"><?php esc_html_e( 'Show next theme' ); ?></span></button> <button type="button" class="close dashicons dashicons-no"><span class="screen-reader-text"><?php esc_html_e( 'Close details dialog' ); ?></span></button> </div> <div class="theme-about wp-clearfix"> <div class="theme-screenshots"> <# if ( data.screenshot && data.screenshot[0] ) { #> <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> <# } else { #> <div class="screenshot blank"></div> <# } #> </div> <div class="theme-info"> <# if ( data.active ) { #> <span class="current-label"><?php esc_html_e( 'Current Theme' ); ?></span> <# } #> <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"> <?php /* translators: %s: Theme version. */ printf( esc_html__( 'Version: %s' ), '{{ data.version }}' ); ?> </span></h2> <h3 class="theme-author"> <?php /* translators: %s: Theme author link. */ printf( esc_html__( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?> </h3> <# if ( data.stars && 0 != data.num_ratings ) { #> <div class="theme-rating"> {{{ data.stars }}} <a class="num-ratings" target="_blank" href="{{ data.reviews_url }}"> <?php printf( '%1$s <span class="screen-reader-text">%2$s</span>', /* translators: %s: Number of ratings. */ sprintf( esc_html__( '(%s ratings)' ), '{{ data.num_ratings }}' ), /* translators: Accessibility text. */ esc_html__( '(opens in a new tab)' ) ); ?> </a> </div> <# } #> <# if ( data.hasUpdate ) { #> <# if ( data.updateResponse.compatibleWP && data.updateResponse.compatiblePHP ) { #> <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> <h3 class="notice-title"><?php esc_html_e( 'Update Available' ); ?></h3> {{{ data.update }}} </div> <# } else { #> <div class="notice notice-error notice-alt notice-large" data-slug="{{ data.id }}"> <h3 class="notice-title"><?php esc_html_e( 'Update Incompatible' ); ?></h3> <p> <# if ( ! data.updateResponse.compatibleWP && ! data.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your versions of WordPress and PHP.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.updateResponse.compatibleWP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of WordPress.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ et_core_intentionally_unescaped( __( 'There is a new version of %s available, but it doesn’t work with your version of PHP.' ), 'html' ), '{{{ data.name }}}' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p> </div> <# } #> <# } #> <# if ( data.parent ) { #> <p class="parent-theme"> <?php printf( /* translators: %s: Theme name. */ esc_html__( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?> </p> <# } #> <# if ( ! data.compatibleWP || ! data.compatiblePHP ) { #> <div class="notice notice-error notice-alt notice-large"><p> <# if ( ! data.compatibleWP && ! data.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your versions of WordPress and PHP.' ), 'html' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.compatibleWP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of WordPress.' ), 'html' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Please update WordPress</a>.' ), 'html' ), esc_url( self_admin_url( 'update-core.php' ) ) ); } ?> <# } else if ( ! data.compatiblePHP ) { #> <?php echo et_core_intentionally_unescaped( __( 'This theme doesn’t work with your version of PHP.' ), 'html' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . et_core_intentionally_unescaped( __( '<a href="%s">Learn more about updating PHP</a>.' ), 'html' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p></div> <# } #> <p class="theme-description">{{{ data.description }}}</p> <# if ( data.tags ) { #> <p class="theme-tags"><span><?php esc_html_e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> <# } #> </div> </div> <div class="theme-actions"> <# if ( data.active ) { #> <button type="button" class="button button-primary customize-theme"><?php esc_html_e( 'Customize' ); ?></button> <# } else if ( 'installed' === data.type ) { #> <?php if ( current_user_can( 'delete_themes' ) ) { ?> <# if ( data.actions && data.actions['delete'] ) { #> <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php esc_html_e( 'Delete' ); ?></a> <# } #> <?php } ?> <# if ( data.compatibleWP && data.compatiblePHP ) { #> <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php esc_html_e( 'Live Preview' ); ?></button> <# } else { #> <button class="button button-primary disabled"><?php esc_html_e( 'Live Preview' ); ?></button> <# } #> <# } else { #> <# if ( data.compatibleWP && data.compatiblePHP ) { #> <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php esc_html_e( 'Install' ); ?></button> <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php esc_html_e( 'Install & Preview' ); ?></button> <# } else { #> <button type="button" class="button disabled"><?php echo esc_html_x( 'Cannot Install', 'theme' ); ?></button> <button type="button" class="button button-primary disabled"><?php esc_html_e( 'Install & Preview' ); ?></button> <# } #> <# } #> </div> </div> </script> <?php } /** * Deactivate incompatible plugin once it's activated. * * The code is backported from WP 5.5 partially. However, not all the code is inlcuded * here because: * - We can't add `RequiresWP: Requires at least` & `RequiresPHP: Requires PHP` plugin * headers via `extra_plugin_headers` hook because the keys & values will be combined. * - On WP 5.3, it requires readme.txt instead of main plugin file. We can simplify it * by directly access main plugin file. * * @see {validate_plugin_requirements()} * * @since 4.7.0 * * @param string $plugin Main plugin file name. */ public static function maybe_deactivate_incompatible_plugin( $plugin ) { $plugin_file = WP_PLUGIN_DIR . '/' . $plugin; // Ensure the main plugin file exists. if ( ! file_exists( $plugin_file ) ) { return; } // Get WP & PHP compatibility info. $plugin_headers = get_file_data( $plugin_file, array( 'Name' => 'Plugin Name', 'RequiresWP' => 'Requires at least', 'RequiresPHP' => 'Requires PHP', ), 'plugin' ); $requirements = array( 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '', 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '', ); if ( ! function_exists( 'is_wp_version_compatible' ) || ! function_exists( 'is_php_version_compatible' ) ) { require_once plugin_dir_path( $plugin_file ) . 'core/wp_functions.php'; } // Check version compatibility. $compatible_wp = is_wp_version_compatible( $requirements['requires'] ); $compatible_php = is_php_version_compatible( $requirements['requires_php'] ); /* translators: %s: URL to Update PHP page. */ $php_update_message = '</p><p>' . sprintf( __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); $annotation = wp_get_update_php_annotation(); if ( $annotation ) { $php_update_message .= '</p><p><em>' . $annotation . '</em>'; } // Decide whether current plugin is compatible and should be activated or not. $result = true; if ( ! $compatible_wp && ! $compatible_php ) { $result = new WP_Error( 'plugin_wp_php_incompatible', '<p>' . sprintf( /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */ _x( '<strong>Error:</strong> Current versions of WordPress (%1$s) and PHP (%2$s) do not meet minimum requirements for %3$s. The plugin requires WordPress %4$s and PHP %5$s.', 'plugin' ), get_bloginfo( 'version' ), phpversion(), $plugin_headers['Name'], $requirements['requires'], $requirements['requires_php'] ) . $php_update_message . '</p>' ); } elseif ( ! $compatible_php ) { $result = new WP_Error( 'plugin_php_incompatible', '<p>' . sprintf( /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */ _x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ), phpversion(), $plugin_headers['Name'], $requirements['requires_php'] ) . $php_update_message . '</p>' ); } elseif ( ! $compatible_wp ) { $result = new WP_Error( 'plugin_wp_incompatible', '<p>' . sprintf( /* translators: 1: Current WordPress version, 2: Plugin name, 3: Required WordPress version. */ _x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ), get_bloginfo( 'version' ), $plugin_headers['Name'], $requirements['requires'] ) . '</p>' ); } if ( is_wp_error( $result ) ) { wp_die( $result ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Can't call et_core_intentionally_unescaped function because it's fired during plugin activation hook. } } } endif; VersionRollback.php 0000644 00000044237 15222651263 0010372 0 ustar 00 <?php // phpcs:disable Generic.WhiteSpace.ScopeIndent -- our preference is to not indent the whole inner function in this scenario. if ( ! class_exists( 'ET_Core_VersionRollback' ) ) : /** * Handles version rollback. * * @since 3.10 * * @private * * @package ET\Core\VersionRollback */ class ET_Core_VersionRollback { /** * Product name. * * @var string */ protected $product_name = ''; /** * Product shortname. * * @var string */ protected $product_shortname = ''; /** * Current product version. * * @var string */ protected $product_version = ''; /** * Is rollback service enabled. * * @var bool */ protected $enabled = false; /** * API Username. * * @var string */ protected $api_username = ''; /** * API Key. * * @var string */ protected $api_key = ''; /** * ET_Core_VersionRollback constructor. * * @since 3.10 * * @param string $product_name Product name. * @param string $product_shortname Product shortname. * @param string $product_version Product version. */ public function __construct( $product_name, $product_shortname, $product_version ) { $this->product_name = sanitize_text_field( $product_name ); $this->product_shortname = sanitize_text_field( $product_shortname ); $this->product_version = sanitize_text_field( $product_version ); if ( ! $options = get_site_option( 'et_automatic_updates_options' ) ) { $options = get_option( 'et_automatic_updates_options' ); } $this->api_username = isset( $options['username'] ) ? sanitize_text_field( $options['username'] ) : ''; $this->api_key = isset( $options['api_key'] ) ? sanitize_text_field( $options['api_key'] ) : ''; } /** * Enqueue assets. * * @since ?.? Script `et-core-version-rollback` now loads in footer. * @since 3.10 */ public function assets() { wp_enqueue_style( 'et-core-version-rollback', ET_CORE_URL . 'admin/css/version-rollback.css', array( 'et-core-admin', ), ET_CORE_VERSION ); wp_enqueue_script( 'et-core-version-rollback', ET_CORE_URL . 'admin/js/version-rollback.js', array( 'jquery', 'jquery-ui-tabs', 'jquery-form', 'et-core-admin', ), ET_CORE_VERSION, true ); wp_localize_script( 'et-core-version-rollback', 'etCoreVersionRollbackI18n', array( 'unknownError' => esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ), ) ); } /** * Get previous installed version, if any. * * @since 3.10 * * @return string */ protected function _get_previous_installed_version() { return et_get_option( "{$this->product_shortname}_previous_installed_version", '' ); } /** * Set previous installed version. * * @since 3.10 * * @param string $version * * @return void */ protected function _set_previous_installed_version( $version ) { et_update_option( "{$this->product_shortname}_previous_installed_version", sanitize_text_field( $version ) ); } /** * Get latest installed version, if any. * * @since 3.10 * * @return string */ protected function _get_latest_installed_version() { return et_get_option( "{$this->product_shortname}_latest_installed_version", '' ); } /** * Set latest installed version. * * @since 3.10 * * @param string $version * * @return void */ protected function _set_latest_installed_version( $version ) { et_update_option( "{$this->product_shortname}_latest_installed_version", sanitize_text_field( $version ) ); } /** * Check if the product has already been rolled back. * * @since 3.10 * * @return bool */ protected function _is_rolled_back() { return version_compare( $this->_get_latest_installed_version(), $this->_get_previous_installed_version(), '<=' ); } /** * Get unique ajax action. * * @since 3.10 * * @return string */ protected function _get_ajax_action() { return 'et_core_version_rollback'; } /** * Enable update rollback. * * @since 3.10 * * @return void */ public function enable() { if ( $this->enabled ) { return; } $this->enabled = true; add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) ); add_action( 'wp_ajax_' . $this->_get_ajax_action(), array( $this, 'ajax_rollback' ) ); // Update version number when theme is manually replaced. add_action( 'admin_init', array( $this, 'store_previous_version_number' ) ); // Update version number when theme is activated. add_action( 'after_switch_theme', array( $this, 'store_previous_version_number' ) ); // Update version number when theme is updated. add_action( 'upgrader_process_complete', array( $this, 'store_previous_version_number' ), 10, 0 ); } /** * Handle REST API requests to rollback. * * @since 3.10 * * @return void */ public function ajax_rollback() { if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], $this->_get_ajax_action() ) ) { wp_send_json_error( array( 'errorCode' => 'et_unknown', 'error' => esc_html__( 'Security check failed. Please refresh and try again.', 'et-core' ), ), 400 ); } if ( ! current_user_can( 'install_themes' ) ) { wp_send_json_error( array( 'errorCode' => 'et_unknown', 'error' => esc_html__( 'You don\'t have sufficient permissions to access this page.', 'et-core' ), ), 400 ); } if ( $this->_is_rolled_back() ) { $error = ' <p> ' . et_get_safe_localization( sprintf( __( 'You\'re currently rolled back to <strong>Version %1$s</strong> from <strong>Version %2$s</strong>.', 'et-core' ), esc_html( $this->_get_latest_installed_version() ), esc_html( $this->_get_previous_installed_version() ) ) ) . ' </p> <p> ' . et_get_safe_localization( sprintf( __( 'Update to the latest version to unlock the full power of %1$s. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ), esc_html( $this->product_name ), esc_url( $this->_get_update_documentation_url() ) ) ) . ' </p> '; wp_send_json_error( array( 'errorCode' => 'et_unknown', 'error' => $error, ), 400 ); } $success = $this->rollback(); if ( is_wp_error( $success ) ) { $error = $success->get_error_message(); if ( $success->get_error_code() === 'et_version_rollback_blocklisted' ) { $error = ' <p> ' . et_get_safe_localization( sprintf( __( 'For privacy and security reasons, you cannot rollback to <strong>Version %1$s</strong>.', 'et-core' ), esc_html( $this->_get_previous_installed_version() ) ) ) . ' </p> <p> <a href="' . esc_url( $this->_get_update_documentation_url() ) . '" target="_blank"> ' . esc_html__( 'Learn more here.', 'et-core' ) . ' </a> </p> '; } wp_send_json_error( array( 'errorIsUnrecoverable' => in_array( $success->get_error_code(), array( 'et_version_rollback_not_available', 'et_version_rollback_blocklisted' ) ), 'errorCode' => $success->get_error_code(), 'error' => $error, ), 400 ); } wp_send_json_success(); } /** * Execute a version rollback. * * @since 3.10 * * @return bool|WP_Error */ public function rollback() { // Load versions before rollback so they are not affected. $previous_version = $this->_get_previous_installed_version(); $latest_version = $this->_get_latest_installed_version(); $api = new ET_Core_API_ElegantThemes( $this->api_username, $this->api_key ); $available = $api->is_product_available( $this->product_name, $previous_version ); if ( is_wp_error( $available ) ) { $major_minor = implode( '.', array_slice( explode( '.', $previous_version ), 0, 2 ) ); if ( $major_minor . '.0' === $previous_version ) { // Skip the trailing 0 in the version number and retry. $previous_version = $major_minor; $available = $api->is_product_available( $this->product_name, $previous_version ); } if ( is_wp_error( $available ) ) { return $available; } } $download_url = $api->get_download_url( $this->product_name, $previous_version ); // Buffer and discard output as upgrader classes still output content even if the upgrader skin is silent. $buffer_started = ob_start(); $result = $this->_install_theme( $download_url ); if ( $buffer_started ) { ob_end_clean(); } if ( is_wp_error( $result ) ) { return $result; } if ( true !== $result ) { return new WP_Error( 'et_unknown', esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ) ); } /** * Fires after successful product version rollback. * * @since 3.26 * * @param string $product_short_name - The short name of the product rolling back. * @param string $rollback_from_version - The product version rolling back from. * @param string $rollback_to_version - The product version rolling back to. */ do_action( 'et_after_version_rollback', $this->product_shortname, $latest_version, $previous_version ); // Swap version numbers after a successful rollback. $this->_set_previous_installed_version( $latest_version ); $this->_set_latest_installed_version( $previous_version ); } /** * Install a theme overwriting it if it already exists. * Copied from Theme_Upgrader::install() due to lack of control over the clear_desination argument. * * @see Theme_Upgrader::install() @ WordPress 4.9.4 * * @since 3.10 * * @param string $package * * @return bool|WP_Error */ protected function _install_theme( $package ) { require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); $upgrader = new Theme_Upgrader( new ET_Core_LIB_SilentThemeUpgraderSkin() ); $defaults = array( 'clear_update_cache' => true, ); $parsed_args = wp_parse_args( array(), $defaults ); $upgrader->init(); $upgrader->install_strings(); add_filter('upgrader_source_selection', array( $upgrader, 'check_package' ) ); add_filter('upgrader_post_install', array( $upgrader, 'check_parent_theme_filter' ), 10, 3 ); if ( $parsed_args['clear_update_cache'] ) { // Clear cache so wp_update_themes() knows about the new theme. add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 ); } $upgrader->run( array( 'package' => $package, 'destination' => get_theme_root(), 'clear_destination' => true, // Overwrite theme. 'clear_working' => true, 'hook_extra' => array( 'type' => 'theme', 'action' => 'install', ), ) ); remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 ); remove_filter( 'upgrader_source_selection', array( $upgrader, 'check_package' ) ); remove_filter( 'upgrader_post_install', array( $upgrader, 'check_parent_theme_filter' ) ); if ( ! $upgrader->result || is_wp_error( $upgrader->result ) ) { return $upgrader->result; } // Refresh the Theme Update information. wp_clean_themes_cache( $parsed_args['clear_update_cache'] ); return true; } /** * Get update documentation url for the product. * * @since 3.10 * * @return string */ protected function _get_update_documentation_url() { return "https://www.elegantthemes.com/documentation/{$this->product_shortname}/update-{$this->product_shortname}/"; } /** * Return ePanel option. * * @since 3.10 * * @return array */ public function get_epanel_option() { return array( 'name' => esc_html__( 'Version Rollback', 'et-core' ), 'id' => 'et_version_rollback', 'type' => 'callback_function', 'function_name' => array( $this, 'render_epanel_option' ), 'desc' => et_get_safe_localization( __( 'If you recently updated to a new version and are experiencing problems, you can easily roll back to the previously-installed version. We always recommend using the latest version and testing updates on a staging site. However, if you run into problems after updating you always have the option to roll back.', 'et-core' ) ), ); } /** * Render ePanel option. * * @since 3.10 * * @return void */ public function render_epanel_option() { $previous = $this->_get_previous_installed_version(); $modal_renderer = array( $this, 'render_epanel_no_previous_version_modal' ); if ( ! empty( $previous ) ) { $modal_renderer = array( $this, 'render_epanel_confirm_rollback_modal' ); if ( $this->_is_rolled_back() ) { $modal_renderer = array( $this, 'render_epanel_already_rolled_back_modal' ); } } add_action( 'admin_footer', $modal_renderer ); ?> <button type="button" class="et-button et-button--simple" data-et-core-modal=".et-core-version-rollback-modal"> <?php esc_html_e( 'Rollback to the previous version', 'et-core' ); ?> </button> <?php } /** * Render ePanel warning modal when no previous supported version has been used. * * @since 3.10 * * @return void */ public function render_epanel_no_previous_version_modal() { ?> <div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal et-core-modal-actionless"> <div class="et-core-modal"> <div class="et-core-modal-header"> <h3 class="et-core-modal-title"> <?php esc_html_e( 'Version Rollback', 'et-core' ); ?> </h3> <a href="#" class="et-core-modal-close" data-et-core-modal="close"></a> </div> <div id="et-core-version-rollback-modal-content"> <div class="et-core-modal-content"> <p> <?php printf( esc_html__( 'The previously used version of %1$s does not support version rollback.', 'et-core' ), esc_html( $this->product_name ) ); ?> </p> </div> </div> </div> </div> <?php } /** * Render ePanel confirmation modal for rollback. * * @since 3.10 * * @return void */ public function render_epanel_confirm_rollback_modal() { $action = $this->_get_ajax_action(); $url = add_query_arg( array( 'action' => $action, 'nonce' => wp_create_nonce( $action ), ), admin_url( 'admin-ajax.php' ) ); ?> <div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal"> <div class="et-core-modal"> <div class="et-core-modal-header"> <h3 class="et-core-modal-title"> <?php esc_html_e( 'Version Rollback', 'et-core' ); ?> </h3> <a href="#" class="et-core-modal-close" data-et-core-modal="close"></a> </div> <div id="et-core-version-rollback-modal-content"> <div class="et-core-modal-content"> <p> <?php echo et_get_safe_localization( sprintf( __( 'You\'ll be rolled back to <strong>Version %1$s</strong> from the current <strong>Version %2$s</strong>.', 'et-core' ), esc_html( $this->_get_previous_installed_version() ), esc_html( $this->_get_latest_installed_version() ) ) ); ?> </p> <p> <?php echo et_get_safe_localization( sprintf( __( 'Rolling back will reinstall the previous version of %1$s. You will be able to update to the latest version at any time. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ), esc_html( $this->product_name ), esc_url( $this->_get_update_documentation_url() ) ) ); ?> </p> <p> <strong> <?php esc_html_e( 'Make sure you have a full site backup before proceeding.', 'et-core' ); ?> </strong> </p> </div> <a class="et-core-modal-action et-core-version-rollback-confirm" href="<?php echo esc_url( $url ); ?>"> <?php esc_html_e( 'Rollback to the previous version', 'et-core' ); ?> </a> </div> </div> </div> <?php } /** * Render ePanel warning modal when a rollback has already been done. * * @since 3.10 * * @return void */ public function render_epanel_already_rolled_back_modal() { ?> <div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal"> <div class="et-core-modal"> <div class="et-core-modal-header"> <h3 class="et-core-modal-title"> <?php esc_html_e( 'Version Rollback', 'et-core' ); ?> </h3> <a href="#" class="et-core-modal-close" data-et-core-modal="close"></a> </div> <div id="et-core-version-rollback-modal-content"> <div class="et-core-modal-content"> <p> <?php echo et_get_safe_localization( sprintf( __( 'You\'re currently rolled back to <strong>Version %1$s</strong> from <strong>Version %2$s</strong>.', 'et-core' ), esc_html( $this->_get_latest_installed_version() ), esc_html( $this->_get_previous_installed_version() ) ) ); ?> </p> <p> <?php echo et_get_safe_localization( sprintf( __( 'Update to the latest version to unlock the full power of %1$s. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ), esc_html( $this->product_name ), esc_url( $this->_get_update_documentation_url() ) ) ); ?> </p> </div> <a class="et-core-modal-action" href="<?php echo esc_url( admin_url( 'update-core.php' ) ); ?>"> <?php esc_html_e( 'Update to the Latest Version', 'et-core' ); ?> </a> </div> </div> </div> <?php } /** * Store latest and previous installed version. * * @since 3.10 * * @return void; */ public function store_previous_version_number() { $previous_installed_version = $this->_get_previous_installed_version(); $latest_installed_version = $this->_get_latest_installed_version(); // Get the theme version since the files may have changed but // we are still executing old code from memory. $theme_version = et_get_theme_version(); if ( $latest_installed_version === $theme_version ) { return; } if ( empty( $latest_installed_version ) ) { $latest_installed_version = $theme_version; } if ( version_compare( $theme_version, $latest_installed_version, '!=') ) { $previous_installed_version = $latest_installed_version; $latest_installed_version = $theme_version; } /** * Fires after new version number is updated. * * @since 4.10.0 */ do_action( 'et_store_before_new_version_update' ); $this->_set_previous_installed_version( $previous_installed_version ); $this->_set_latest_installed_version( $latest_installed_version ); /** * Fires after new version number is updated. * * @since 4.10.0 */ do_action( 'et_store_after_new_version_update' ); } } endif; Logger.php 0000644 00000007324 15222651263 0006506 0 ustar 00 <?php class ET_Core_Logger { /** * @var ET_Core_Data_Utils */ protected static $_; /** * Checksum for every log message output during the current request. * * @var string[] */ protected static $HISTORY = array(); /** * Writes a message to the debug log if it hasn't already been written once. * * @since 3.10 * * @param mixed $message * @param int $bt_index * @param boolean $log_ajax Whether or not to log on AJAX calls. */ protected static function _maybe_write_log( $message, $bt_index = 4, $log_ajax = true ) { global $ET_IS_TESTING_DEPRECATIONS; if ( ! is_scalar( $message ) ) { $message = print_r( $message, true ); } $message = (string) $message; $hash = md5( $message ); if ( ! $log_ajax && wp_doing_ajax() ) { return; } if ( $ET_IS_TESTING_DEPRECATIONS ) { trigger_error( $message ); } else if ( getenv( 'CI' ) || ! in_array( $hash, self::$HISTORY ) ) { self::$HISTORY[] = $hash; self::_write_log( $message, $bt_index ); } } /** * Writes a message to the WP Debug and PHP Error logs. * * @param string $message * @param int $bt_index */ private static function _write_log( $message, $bt_index = 4 ) { $message = trim( $message ); $backtrace = debug_backtrace( 1 ); $class = ''; $function = ''; if ( ! isset( $backtrace[ $bt_index ] ) ) { while ( $bt_index > 0 && ! isset( $backtrace[ $bt_index ] ) ) { $bt_index--; } // We need two stacks to get all the data we need so let's go down one more $bt_index--; } $stack = $backtrace[ $bt_index ]; $file = self::$_->array_get( $stack, 'file', '<unknown file>' ); $line = self::$_->array_get( $stack, 'line', '<unknown line>' ); // Name of the function and class (if applicable) are in the previous stack (stacks are in reverse order) $stack = $backtrace[ $bt_index + 1 ]; $class = self::$_->array_get( $stack, 'class', '' ); $function = self::$_->array_get( $stack, 'function', '<unknown function>' ); if ( $class ) { $class .= '::'; } if ( '<unknown file>' !== $file ) { $file = _et_core_normalize_path( $file ); $parts = explode( '/', $file ); $parts = array_slice( $parts, -2 ); $file = ".../{$parts[0]}/{$parts[1]}"; } $message = " {$file}:{$line} {$class}{$function}():\n{$message}\n"; error_log( $message ); } /** * Writes message to the logs if {@link WP_DEBUG} is `true`, otherwise does nothing. * * @since 1.1.0 * * @param mixed $message * @param int $bt_index * @param boolean $log_ajax Whether or not to log on AJAX calls. */ public static function debug( $message, $bt_index = 4, $log_ajax = true ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { self::_maybe_write_log( $message, $bt_index, $log_ajax ); } } public static function disable_php_notices() { $error_reporting = error_reporting(); $notices_enabled = $error_reporting & E_NOTICE; if ( $notices_enabled ) { error_reporting( $error_reporting & ~E_NOTICE ); } } /** * Writes an error message to the logs regardless of whether or not debug mode is enabled. * * @since 1.1.0 * * @param mixed $message * @param int $bt_index */ public static function error( $message, $bt_index = 4 ) { self::_maybe_write_log( $message, $bt_index ); } public static function enable_php_notices() { $error_reporting = error_reporting(); $notices_enabled = $error_reporting & E_NOTICE; if ( ! $notices_enabled ) { error_reporting( $error_reporting | E_NOTICE ); } } public static function initialize() { self::$_ = ET_Core_Data_Utils::instance(); } public static function php_notices_enabled() { $error_reporting = error_reporting(); return $error_reporting & E_NOTICE; } } ET_Core_Logger::initialize(); Updates.php 0000644 00000105650 15222651263 0006675 0 ustar 00 <?php // phpcs:disable Generic.WhiteSpace.ScopeIndent.Incorrect, Generic.WhiteSpace.ScopeIndent.IncorrectExact -- Until we reformat this entire file, this rule makes reviewing PRs very difficult. if ( ! class_exists( 'ET_Core_Updates' ) ): /** * Handles the updates workflow. * * @private * * @package ET\Core\Updates */ final class ET_Core_Updates { protected $core_url; protected $options; protected $account_status; protected $product_version; protected $all_et_products_domains; protected $upgrading_et_product; protected $up_to_date_products_data; // class version protected $version; // Is a product being upgraded in the current API request. protected $upgraded_a_product = false; private static $_this; function __construct( $core_url, $product_version ) { global $wp_version; // Don't allow more than one instance of the class if ( isset( self::$_this ) ) { wp_die( sprintf( esc_html__( '%s: You cannot create a second instance of this class.', 'et-core' ), esc_html( get_class( $this ) ) ) ); } self::$_this = $this; $this->core_url = $core_url; $this->version = '1.2'; $this->up_to_date_products_data = array(); $this->product_version = $product_version; $this->get_options(); $this->upgrading_et_product = false; $this->update_product_domains(); $this->maybe_force_update_requests(); add_filter( 'wp_prepare_themes_for_js', array( $this, 'replace_theme_update_notification' ) ); add_filter( 'upgrader_package_options', array( $this, 'check_upgrading_product' ) ); // The 4th parameter, $hook_extra was added in WordPress 5.5.0. if ( version_compare( $wp_version, '5.5.0', '>=' ) ) { add_filter( 'upgrader_pre_download', array( $this, 'update_error_message' ), 20, 4 ); } else { add_filter( 'upgrader_pre_download', array( $this, 'update_error_message' ), 20, 3 ); } add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_plugins_updates' ) ); add_filter( 'plugins_api', array( $this, 'maybe_modify_plugins_changelog' ), 20, 3 ); add_filter( 'pre_set_site_transient_update_themes', array( $this, 'check_themes_updates' ) ); add_filter( 'self_admin_url', array( $this, 'change_plugin_changelog_url' ), 10, 2 ); add_filter( 'admin_url', array( $this, 'change_plugin_changelog_url' ), 10, 2 ); add_filter( 'network_admin_url', array( $this, 'change_plugin_changelog_url' ), 10, 2 ); add_action( 'admin_notices', array( $this, 'maybe_show_account_notice' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'load_scripts_styles' ) ); add_action( 'plugins_loaded', array( $this, 'remove_updater_plugin_actions' ), 30 ); add_action( 'after_setup_theme', array( $this, 'remove_theme_update_actions' ), 11 ); add_action( 'admin_init', array( $this, 'remove_plugin_update_actions' ) ); add_action( 'update_site_option_et_automatic_updates_options', array( $this, 'force_update_requests' ) ); add_action( 'update_option_et_automatic_updates_options', array( $this, 'force_update_requests' ) ); add_action( 'deleted_site_transient', array( $this, 'maybe_reset_et_products_update_transient' ) ); add_action( 'upgrader_process_complete', array( $this, 'upgraded_a_product' ), 9, 0 ); } function check_upgrading_product( $options ) { if ( ! isset( $options['hook_extra'] ) ) { return $options; } $hook_name = isset( $options['hook_extra']['plugin'] ) ? 'plugin' : 'theme'; // set the upgrading_et_product flag if one of ET plugins or themes is about to upgrade if ( isset( $options['hook_extra'][ $hook_name ] ) && in_array( $options['hook_extra'][ $hook_name ], $this->all_et_products_domains[ $hook_name ] ) ) { $this->upgrading_et_product = true; } return $options; } function maybe_append_custom_notification( $plugin_data, $response ) { if ( empty( $response ) ) { $package_available = false; } else { // for themes response is array for plugins - object, so check the format of data to get the correct results $package_available = is_array( $response ) ? ! empty( $response['package'] ) : ! empty( $response->package ); } if ( $package_available ) { return; } $message = empty( $custom_message = $this->get_custom_update_notification_message( $plugin_data['url'] ) ) ? et_get_safe_localization( __( 'For all Elegant Themes products, please <a href="https://www.elegantthemes.com/documentation/divi/update-divi/" target="_blank">authenticate your subscription</a> via the Updates tab in your theme & plugin settings to enable product updates. Make sure that your Username and API Key have been entered correctly.', 'et-core' ) ) : $custom_message; echo "</p><p>{$message}"; } /** * Check if we need to force update options removal in case a customer clicked on "Check Again" button * in the notification area. */ function maybe_force_update_requests() { if ( wp_doing_ajax() ) { return; } if ( empty( $_GET['et_action'] ) || 'update_account_details' !== $_GET['et_action'] ) { return; } if ( empty( $_GET['et_update_account_details_nonce'] ) || ! wp_verify_nonce( $_GET['et_update_account_details_nonce'], 'et_update_account_details' ) ) { return; } $this->force_update_requests(); } function get_custom_update_notification_message( $update_message ) { $is_valid_api_key_status = empty( $account_api_key_status = get_site_option( 'et_account_api_key_status' ) ) || 'invalid' !== $account_api_key_status; if ( $is_valid_api_key_status && false !== strpos( $update_message, '/wp-json/api/v1/changelog/product_id/' ) ) { return et_get_safe_localization( __( '<em>The license for this Divi Marketplace product has expired. Please <a target="_blank" href="https://www.elegantthemes.com/members-area/marketplace/">renew the license</a> to continue receiving product updates and support.</em>', 'et-core' ) ); } if ( false !== strpos( $update_message, 'Automatic update is unavailable for this theme' ) ) { return 'expired' === $this->account_status ? et_get_safe_localization( __( '<em>Your Elegant Themes subscription has expired. You must <a href="https://www.elegantthemes.com/members-area/renew/" target="_blank">renew your account</a> to regain access to product updates and support.</em>', 'et-core' ) ) : et_get_safe_localization( __( '<em>Before you can receive product updates, you must first authenticate your Elegant Themes subscription. To do this, you need to enter both your Elegant Themes Username and your Elegant Themes API Key into the Updates Tab in your theme and plugin settings. To locate your API Key, <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">log in</a> to your Elegant Themes account and navigate to the <strong>Account > API Key</strong> page. <a href="https://www.elegantthemes.com/documentation/divi/update-divi/" target="_blank">Learn more here</a></em>. If you still get this message, please make sure that your Username and API Key have been entered correctly', 'et-core' ) ); } return ''; } function replace_theme_update_notification( $themes_array ) { if ( empty( $themes_array ) ) { return $themes_array; } if ( empty( $this->all_et_products_domains['theme'] ) ) { return $themes_array; } foreach ( $themes_array as $id => $theme_data ) { // replace default error message with custom message for ET themes. if ( ! in_array( $id, $this->all_et_products_domains['theme'] ) || false === strpos( $theme_data['update'], 'Automatic update is unavailable for this theme' ) ) { continue; } if ( ! empty( $custom_message = $this->get_custom_update_notification_message( $theme_data['update'] ) ) ) { $themes_array[ $id ]['update'] = sprintf( '<p>%1$s<br/> %2$s</p>', $theme_data['update'], $custom_message ); } } return $themes_array; } function update_error_message( $reply, $package, $upgrader, $hook_extra = array() ) { if ( ! $this->upgrading_et_product ) { return $reply; } // reset the upgrading_et_product flag $this->upgrading_et_product = false; if ( ! empty( $package ) ) { return $reply; } $hook_name = ! empty( $hook_extra['theme'] ) ? 'theme' : 'plugin'; $site_transient = 'theme' === $hook_name ? get_site_transient( 'et_update_themes' ) : get_site_transient( 'et_update_plugins' ); $changelog_url = ''; if ( isset( $site_transient->response ) && ! empty( $site_transient->response[ $hook_extra[ $hook_name ] ] ) ) { $changelog_url = 'theme' === $hook_name ? $site_transient->response[ $hook_extra[ $hook_name ] ]['url'] : $site_transient->response[ $hook_extra[ $hook_name ] ]->url; } if ( false !== strpos( $changelog_url, '/wp-json/api/v1/changelog/product_id/' ) ) { $error_message = $this->get_custom_update_notification_message( $changelog_url ); } else { $error_message = 'expired' === $this->account_status ? et_get_safe_localization( __( '<em>Your Elegant Themes subscription has expired. You must <a href="https://www.elegantthemes.com/members-area/renew/" target="_blank">renew your account</a> to regain access to product updates and support.</em>', 'et-core' ) ) : et_get_safe_localization( __( '<em>Before you can receive product updates, you must first authenticate your Elegant Themes subscription. To do this, you need to enter both your Elegant Themes Username and your Elegant Themes API Key into the Updates Tab in your theme and plugin settings. To locate your API Key, <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">log in</a> to your Elegant Themes account and navigate to the <strong>Account > API Key</strong> page. <a href="https://www.elegantthemes.com/documentation/divi/update-divi/" target="_blank">Learn more here</a></em>. If you still get this message, please make sure that your Username and API Key have been entered correctly', 'et-core' ) ); } // output custom error message for ET Products if package is empty return new WP_Error( 'no_package', $error_message ); } /** * Get all Elegant Themes products, returned from the API request */ function get_et_api_products() { $products = array( 'theme' => array(), 'plugin' => array(), ); $update_transients = array( 'et_update_themes', 'et_update_plugins', ); foreach ( $update_transients as $update_transient_name ) { $type = 'et_update_themes' === $update_transient_name ? 'theme' : 'plugin'; if ( false !== ( $update_transient = get_site_transient( $update_transient_name ) ) && ! empty( $update_transient->response ) && is_array( $update_transient->response ) ) { $et_product_stylesheet_names = array_keys( $update_transient->response ); foreach ( $et_product_stylesheet_names as $et_product_stylesheet_name ) { $products[ $type ][] = $et_product_stylesheet_name; } } } return $products; } function get_all_et_products() { $checked_et_products = $this->get_et_api_products(); return $checked_et_products; } function remove_theme_update_actions() { remove_filter( 'pre_set_site_transient_update_themes', 'et_check_themes_updates' ); remove_filter( 'site_transient_update_themes', 'et_add_themes_to_update_notification' ); } function remove_plugin_update_actions() { remove_filter( 'pre_set_site_transient_update_plugins', 'et_shortcodes_plugin_check_updates' ); remove_filter( 'site_transient_update_plugins', 'et_shortcodes_plugin_add_to_update_notification' ); } /** * Removes Updater plugin actions and filters, * so it doesn't make additional requests to API * * @return void */ function remove_updater_plugin_actions() { if ( ! class_exists( 'ET_Automatic_Updates' ) ) { return; } $updates_class = ET_Automatic_Updates::get_this(); remove_filter( 'after_setup_theme', array( $updates_class, 'remove_default_updates' ), 11 ); remove_filter( 'init', array( $updates_class, 'remove_default_plugins_updates' ), 20 ); remove_action( 'admin_notices', array( $updates_class, 'maybe_display_expired_message' ) ); } /** * Returns an instance of the object * * @return object */ static function get_this() { return self::$_this; } /** * Adds automatic updates data only if Username and API key options are set * * @param array $send_to_api Data sent to server * @return array Modified data set if Username and API key are set, original data if not */ function maybe_add_automatic_updates_data( $send_to_api ) { if ( $this->options && isset( $this->options['username'] ) && isset( $this->options['api_key'] ) ) { $send_to_api['automatic_updates'] = 'on'; $send_to_api['username'] = urlencode( sanitize_text_field( $this->options['username'] ) ); $send_to_api['api_key'] = sanitize_text_field( $this->options['api_key'] ); $send_to_api = apply_filters( 'et_add_automatic_updates_data', $send_to_api ); } return $send_to_api; } /** * Gets plugin options * * @return void */ function get_options() { if ( ! $this->options = get_site_option( 'et_automatic_updates_options' ) ) { $this->options = get_option( 'et_automatic_updates_options' ); } if ( ! $this->account_status = get_site_option( 'et_account_status' ) ) { $this->account_status = get_option( 'et_account_status' ); } } function load_scripts_styles( $hook ) { if ( 'plugin-install.php' !== $hook ) { return; } wp_enqueue_style( 'et_core_updates', $this->core_url . 'admin/css/updates.css', array(), $this->product_version ); } function add_up_to_date_products_data( $update_data, $settings = array() ) { $settings = $this->process_request_settings( $settings ); $products_category = $settings['is_plugin_response'] ? 'plugins' : 'themes'; if ( ! empty( $this->up_to_date_products_data[ $products_category ] ) ) { $update_data->no_update = $this->up_to_date_products_data[ $products_category ]; } return $update_data; } /** * Merges product information from cached et updates transients * to the main WordPress updates transients. * * @param array $update_transient The main WordPress themes or plugins updates transient. * @param array $et_update_products_data The cached et themes or plugins updates transient. * @return array */ function merge_et_products_response( $update_transient, $et_update_products_data ) { // If $et_update_products_data is empty or both its response and no_update arrays are empty, there is no cached data to merge and we can return the main WordPress transient. if ( empty( $et_update_products_data ) || ( empty( $et_update_products_data->response ) && empty( $et_update_products_data->no_update ) ) ) { return $update_transient; // Return the original transient if true. } // Fields to merge. $merge_data_fields = array( 'response', 'no_update', ); // Merge the fields. foreach ( $merge_data_fields as $data_field_name ) { if ( empty( $et_update_products_data->$data_field_name ) ) { continue; // Skip if the field is empty. } // Get the default response data. $default_response_data = ! empty( $update_transient->$data_field_name ) ? $update_transient->$data_field_name : array(); // Merge the data. $update_transient->$data_field_name = array_merge( $default_response_data, (array) $et_update_products_data->$data_field_name ); } // Check if the response array is empty in the cached transient, which is where products that need to be updated are listed. Before merging, we need to make sure the latest version isn't already installed, in which case it needs to be moved to the no_update array. if ( ! empty( $et_update_products_data->response ) ) { foreach ( $et_update_products_data->response as $product => $data ) { // Get the currently installed product version from the primary WordPress update transient. $installed_version = isset( $update_transient->checked[ $product ] ) ? $update_transient->checked[ $product ] : null; // Get the latest product version from the cached transient, handling both array and object cases. if ( is_array( $data ) ) { $latest_version = isset( $data['new_version'] ) ? $data['new_version'] : null; } elseif ( is_object( $data ) ) { $latest_version = isset( $data->new_version ) ? $data->new_version : null; } // If the currently installed version is the latest version, we need to move the product to the no_update array and remove the product from the response array. if ( $installed_version && $latest_version && version_compare( $installed_version, $latest_version, '>=' ) ) { $update_transient->no_update[ $product ] = $data; unset( $update_transient->response[ $product ] ); } } } // Now we need to do the opposite. Check if the no_update array is empty in the cached transient, which is where products that don't need to be updated are listed. If an update is required, move the product to the response array. if ( ! empty( $et_update_products_data->no_update ) ) { foreach ( $et_update_products_data->no_update as $product => $data ) { // Get the currently installed product version from the primary WordPress update transient. $installed_version = isset( $update_transient->checked[ $product ] ) ? $update_transient->checked[ $product ] : null; // Get the latest product version from the cached transient, handling both array and object cases. if ( is_array( $data ) ) { $latest_version = isset( $data['new_version'] ) ? $data['new_version'] : null; } elseif ( is_object( $data ) ) { $latest_version = isset( $data->new_version ) ? $data->new_version : null; } // If the currently installed version is not the latest version, we need to move the product to the response array and remove the product from the no_update array. if ( $installed_version && $latest_version && version_compare( $installed_version, $latest_version, '<' ) ) { $update_transient->response[ $product ] = $data; unset( $update_transient->no_update[ $product ] ); } } } return $update_transient; // Return the merged transient updated with the most recently cached product info. } function check_plugins_updates( $update_transient ) { global $wp_version; if ( ! isset( $update_transient->response ) ) { return $update_transient; } $plugins = []; $et_update_plugins = get_site_transient( 'et_update_plugins' ); // update_plugins transient gets set two times, so we ensure we make a request once if ( ! $this->upgraded_a_product && isset( $et_update_plugins->last_checked ) && isset( $update_transient->last_checked ) && $et_update_plugins->last_checked > ( $update_transient->last_checked - 10 * MINUTE_IN_SECONDS ) ) { return $this->merge_et_products_response( $update_transient, $et_update_plugins ); } $_plugins = get_plugins(); if ( empty( $_plugins ) ) { return $update_transient; } foreach ( $_plugins as $file => $plugin ) { $update_uri = isset( $plugin['UpdateURI'] ) ? $plugin['UpdateURI'] : ''; $is_et_uri = false !== strpos( $update_uri, 'elegantthemes.com' ); // Continue to the next iteration if the Update URI // is not empty and not using Elegant Themes's domain. if ( ! empty( $update_uri ) && ! $is_et_uri ) { continue; } $plugins[ $file ] = $plugin['Version']; } do_action( 'et_core_updates_before_request' ); $send_to_api = array( 'action' => 'check_all_plugins_updates', 'installed_plugins' => $plugins, 'class_version' => $this->version, ); // Add automatic updates data if Username and API key are set correctly $send_to_api = $this->maybe_add_automatic_updates_data( $send_to_api ); // If we don't have update values cached in the transient, we need to bypass rate limiting. if ( $et_update_plugins ) { $rate_limit = 'true'; } else { $rate_limit = 'false'; } $options = array( 'timeout' => ( ( defined('DOING_CRON') && DOING_CRON ) ? 10 : 3), 'body' => $send_to_api, 'headers' => array( 'rate_limit' => $rate_limit, ), 'user-agent' => 'WordPress/' . $wp_version . '; Plugin Updates/' . ET_CORE_VERSION . '; ' . home_url( '/' ), ); $last_update = new stdClass(); $plugins_request = wp_remote_post( 'https://www.elegantthemes.com/api/api.php', $options ); $plugins_response = []; $et_update_plugins_updated = false; if ( ! is_wp_error( $plugins_request ) && wp_remote_retrieve_response_code( $plugins_request ) === 200 ){ $plugins_response = maybe_unserialize( wp_remote_retrieve_body( $plugins_request ) ); if ( ! empty( $plugins_response ) && is_array( $plugins_response ) ) { $et_update_plugins_updated = true; $request_settings = array( 'is_plugin_response' => true ); $plugins_response = $this->process_additional_response_settings( $plugins_response, $request_settings ); $last_update->checked = $plugins; $last_update->response = $plugins_response; $last_update = $this->add_up_to_date_products_data( $last_update, $request_settings ); $last_update->last_checked = time(); $update_transient = $this->merge_et_products_response( $update_transient, $plugins_response ); set_site_transient( 'et_update_plugins', $last_update ); $this->update_product_domains(); } } // if this is a rate limited or failed request, fallback to the last et_update_plugins value // rather than overwrite it with an empty response if ( ! $et_update_plugins_updated ) { $update_transient = $this->merge_et_products_response( $update_transient, $et_update_plugins ); } return $update_transient; } public function maybe_modify_plugins_changelog( $false, $action, $args ) { if ( 'plugin_information' !== $action ) { return $false; } if ( isset( $args->slug ) ) { $et_update_lb_plugin = get_site_transient( 'et_update_plugins' ); $plugin_basename = sprintf( '%1$s/%1$s.php', sanitize_text_field( $args->slug ) ); if ( isset( $et_update_lb_plugin->response[ $plugin_basename ] ) ) { $plugin_info = $et_update_lb_plugin->response[ $plugin_basename ]; if ( isset( $plugin_info->et_sections_used ) && 'on' === $plugin_info->et_sections_used ) { return $plugin_info; } } } return $false; } function process_account_settings( $response ) { if ( empty( $response['et_account_data'] ) ) { return $response; } $additional_settings_fields = array( 'et_username_status', 'et_api_key_status', 'et_expired_subscription', ); $et_account_data = $response['et_account_data']; $additional_settings = array(); $is_theme_response = is_array( $et_account_data ); foreach ( $additional_settings_fields as $additional_settings_field ) { $field = ''; $field_exists = $is_theme_response ? array_key_exists( $additional_settings_field, $et_account_data ) : ! empty( $et_account_data->$additional_settings_field ); if ( $field_exists ) { $field = $is_theme_response ? $et_account_data[ $additional_settings_field ] : $et_account_data->$additional_settings_field; } $additional_settings[ $additional_settings_field ] = $field; } if ( ! empty( $additional_settings[ 'et_username_status' ] ) && in_array( $additional_settings[ 'et_username_status' ], array( 'active', 'expired', 'not_found' ) ) ) { $this->account_status = sanitize_text_field( $additional_settings['et_username_status'] ); } else { // Set the account status to expired if the response array has 'et_expired_subscription' key $this->account_status = ! empty( $additional_settings[ 'et_expired_subscription' ] ) ? 'expired' : 'active'; } update_site_option( 'et_account_status', $this->account_status ); if ( ! empty( $additional_settings[ 'et_api_key_status' ] ) ) { update_site_option( 'et_account_api_key_status', sanitize_text_field( $additional_settings['et_api_key_status'] ) ); } else { delete_site_option( 'et_account_api_key_status' ); } unset( $response['et_account_data'] ); return $response; } function process_up_to_date_products_settings( $response, $settings ) { if ( empty( $response['et_up_to_date_products'] ) ) { return $response; } $products_category = $settings['is_plugin_response'] ? 'plugins' : 'themes'; $this->up_to_date_products_data[ $products_category ] = $response['et_up_to_date_products']; unset( $response['et_up_to_date_products'] ); return $response; } function process_request_settings( $settings = array() ) { $defaults = array( 'is_plugin_response' => false, ); return array_merge( $defaults, $settings ); } function process_additional_response_settings( $response, $settings = array() ) { if ( empty( $response ) ) { return $response; } $settings = $this->process_request_settings( $settings ); $response = $this->process_account_settings( $response ); $response = $this->process_up_to_date_products_settings( $response, $settings ); return $response; } /** * Sends a request to server, gets current themes versions * * @param object $update_transient Update transient option * @return object Update transient option */ function check_themes_updates( $update_transient ){ global $wp_version; $et_update_themes = get_site_transient( 'et_update_themes' ); // update_themes transient gets set two times, so we ensure we make a request once if ( ! $this->upgraded_a_product && isset( $et_update_themes->last_checked ) && isset( $update_transient->last_checked ) && $et_update_themes->last_checked > ( $update_transient->last_checked - 10 * MINUTE_IN_SECONDS ) ) { return $this->merge_et_products_response( $update_transient, $et_update_themes ); } if ( ! isset( $update_transient->checked ) ) { return $update_transient; } $themes = $update_transient->checked; do_action( 'et_core_updates_before_request' ); $send_to_api = array( 'action' => 'check_theme_updates', 'installed_themes' => $themes, 'class_version' => $this->version, ); // Add automatic updates data if Username and API key are set correctly $send_to_api = $this->maybe_add_automatic_updates_data( $send_to_api ); // If we don't have update values cached in the transient, we need to bypass rate limiting. if ( $et_update_themes ) { $rate_limit = 'true'; } else { $rate_limit = 'false'; } $options = array( 'timeout' => ( ( defined('DOING_CRON') && DOING_CRON ) ? 10 : 3 ), 'body' => $send_to_api, 'headers' => array( 'rate_limit' => $rate_limit, ), 'user-agent' => 'WordPress/' . $wp_version . '; Theme Updates/' . ET_CORE_VERSION . '; ' . home_url( '/' ), ); $last_update = new stdClass(); $theme_request = wp_remote_post( 'https://www.elegantthemes.com/api/api.php', $options ); $theme_response = []; $et_update_themes_updated = false; if ( ! is_wp_error( $theme_request ) && wp_remote_retrieve_response_code( $theme_request ) === 200 ){ $theme_response = maybe_unserialize( wp_remote_retrieve_body( $theme_request ) ); if ( ! empty( $theme_response ) && is_array( $theme_response ) ) { $et_update_themes_updated = true; $theme_response = $this->process_additional_response_settings( $theme_response ); $last_update->checked = $themes; $last_update->response = $theme_response; $last_update = $this->add_up_to_date_products_data( $last_update ); $last_update->last_checked = time(); $update_transient = $this->merge_et_products_response( $update_transient, $last_update ); set_site_transient( 'et_update_themes', $last_update ); $this->update_product_domains(); } } // if this is a rate limited or failed request, fallback to the last et_update_themes value // rather than overwrite it with an empty response if ( ! $et_update_themes_updated ) { $update_transient = $this->merge_et_products_response( $update_transient, $et_update_themes ); } return $update_transient; } function maybe_show_account_notice() { // Don't show notice on onboarding page. if ( isset( $_GET['page'] ) && 'et_onboarding' === $_GET['page'] ) { return; } if ( empty( $this->options['username'] ) || empty( $this->options['api_key'] ) ) { return; } $all_products = $this->get_et_api_products(); $total_products = count( $all_products['theme'] ) + count( $all_products['plugin'] ); if ( 1 === $total_products && et_()->includes( $all_products['plugin'], 'Divi Dash' ) ) { // Don't show the notice when Divi Dash is the only product return; } $output = ''; $messages = array(); $account_api_key_status = get_site_option( 'et_account_api_key_status' ); $is_expired_account = 'expired' === $this->account_status; $is_invalid_account = 'not_found' === $this->account_status; if ( ! $is_expired_account && ! $is_invalid_account && empty( $account_api_key_status ) ) { return; } if ( $is_expired_account ) { $messages[] = et_get_safe_localization( __( 'Your Elegant Themes subscription has expired. You must <a href="https://www.elegantthemes.com/members-area/" target="_blank">renew your account</a> to regain access to product updates and support. To ensure compatibility and security, it is important to always keep your themes and plugins updated.', 'et-core' ) ); } else if ( $is_invalid_account ) { $messages[] = et_get_safe_localization( __( 'The Elegant Themes username you entered is invalid. Please enter a valid username to receive product updates. If you forgot your username you can <a href="https://www.elegantthemes.com/members-area/retrieve-username/" target="_blank">request it here</a>.', 'et-core' ) ); } if ( ! empty( $account_api_key_status ) ) { switch ( $account_api_key_status ) { case 'deactivated': $status = 'not active'; break; default: $status = 'invalid'; break; } $messages[] = et_get_safe_localization( __( sprintf( 'The Elegant Themes API key you entered is %1$s. Please make sure that your API has been entered correctly and that it is <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">enabled</a> in your account.', $status ), 'et-core' ) ); } foreach ( $messages as $message ) { $output .= sprintf( '<p>%1$s</p>', $message ); } if ( empty( $output ) ) { return; } $dashboard_url = add_query_arg( 'et_action', 'update_account_details', admin_url( 'update-core.php' ) ); printf( '<div class="notice notice-warning"> %1$s <p><a href="%2$s">%3$s</a></p> </div>', $output, esc_url( wp_nonce_url( $dashboard_url, 'et_update_account_details', 'et_update_account_details_nonce' ) ), esc_html__( 'Check Again', 'et-core' ) ); } function change_plugin_changelog_url( $url, $path ) { if ( 0 !== strpos( $path, 'plugin-install.php?tab=plugin-information&plugin=' ) ) { return $url; } $matches = array(); $update_transient = get_site_transient( 'et_update_plugins' ); $et_updated_plugins_data = get_transient( 'et_updated_plugins_data' ); $has_last_checked = ! empty( $update_transient->last_checked ) && ! empty( $et_updated_plugins_data->last_checked ); if ( ! is_object( $update_transient ) ) { return $url; } /* * Attempt to use a cached list of updated plugins. * Re-save the list, whenever the update transient last checked time changes. */ if ( false === $et_updated_plugins_data || ( $has_last_checked && $update_transient->last_checked !== $et_updated_plugins_data->last_checked ) ) { $et_updated_plugins_data = new stdClass(); if ( ! empty( $update_transient->last_checked ) ) { $et_updated_plugins_data->last_checked = $update_transient->last_checked; } foreach ( $update_transient->response as $response_plugin_settings ) { $slug = sanitize_text_field( $response_plugin_settings->slug ); $et_updated_plugins_data->changelogs[ $slug ] = esc_url_raw( $response_plugin_settings->url . '?TB_iframe=true&width=1024&height=800' ); } set_transient( 'et_updated_plugins_data', $et_updated_plugins_data ); } if ( ! empty( $update_transient->no_update ) ) { foreach ( $update_transient->no_update as $no_update_plugin_settings ) { $slug = sanitize_text_field( $no_update_plugin_settings->slug ); $et_updated_plugins_data->changelogs[ $slug ] = esc_url_raw( $no_update_plugin_settings->url . '?TB_iframe=true&width=1024&height=800' ); } } preg_match( '/plugin=([^&]*)/', $path, $matches ); $current_plugin_slug = $matches[1]; // Check if we're dealing with a product that has a custom changelog URL if ( ! empty( $et_updated_plugins_data->changelogs[ $current_plugin_slug ] ) ) { $url = esc_url_raw( $et_updated_plugins_data->changelogs[ $current_plugin_slug ] ); } return $url; } function force_update_requests() { $update_transients = array( 'update_themes', 'update_plugins', 'et_update_themes', 'et_update_plugins', ); foreach ( $update_transients as $update_transient ) { if ( get_site_transient( $update_transient ) ) { delete_site_transient( $update_transient ); } } } function update_product_domains() { $this->all_et_products_domains = $this->get_all_et_products(); $append_notification_action_name = 'maybe_append_custom_notification'; // update notifications for ET products if needed foreach ( array( 'theme', 'plugin' ) as $product_type ) { if ( empty( $this->all_et_products_domains[ $product_type] ) ) { continue; } foreach ( $this->all_et_products_domains[ $product_type ] as $product_key ) { $action_name = sanitize_text_field( sprintf( 'in_%1$s_update_message-%2$s', $product_type, $product_key ) ); if ( has_action( $action_name, array( $this, $append_notification_action_name ) ) ) { continue; } add_action( $action_name, array( $this, $append_notification_action_name ), 10, 2 ); } } } /** * Delete Elegant Themes update products transient, whenever default WordPress update transient gets removed */ function maybe_reset_et_products_update_transient( $transient_name ) { // Transient names for update transients we're interested in. $update_transients_names = array( 'update_themes' => 'et_update_themes', 'update_plugins' => 'et_update_plugins', ); // Check if the transient name is one of the update transients we're interested in. if ( empty( $update_transients_names[ $transient_name ] ) ) { return; } // Check the last_checked time in the transient, and only delete it if it's older than 24 hours. $et_update_transient = get_site_transient( $update_transients_names[ $transient_name ] ); if ( ! empty( $et_update_transient->last_checked ) && $et_update_transient->last_checked > ( time() - DAY_IN_SECONDS ) ) { return; } // Delete the ET update transient, because it's older than 24 hours. delete_site_transient( $update_transients_names[ $transient_name ] ); } /** * Detects if the current API request is related to a product update. * In this case, we need to allow the request to bypass rate limiting * since the update process requests two requests. */ function upgraded_a_product() { $this->upgraded_a_product = true; } } endif; if ( ! function_exists( 'et_core_enable_automatic_updates' ) ) : function et_core_enable_automatic_updates( $deprecated, $version ) { if ( ! is_admin() && ! wp_doing_cron() ) { return; } if ( isset( $GLOBALS['et_core_updates'] ) ) { return; } if ( defined( 'ET_CORE_URL' ) ) { $url = ET_CORE_URL; } else { $url = trailingslashit( $deprecated ) . 'core/'; } $GLOBALS['et_core_updates'] = new ET_Core_Updates( $url, $version ); } endif; Portability.php 0000644 00000316770 15222651263 0007601 0 ustar 00 <?php /** * Import and Export data. * * @package Core\Portability */ /** * Handles the portability workflow. * * @package ET\Core\Portability */ class ET_Core_Portability { /** * Current instance. * * @since 2.7.0 * * @type object */ public $instance; /** * @var ET_Core_Data_Utils */ protected static $_; /** * Whether or not an import is in progress. * * @since 3.0.99 * * @var bool */ protected static $_doing_import = false; /** * Constructor. * * @param string $context Portability context previously registered. */ public function __construct( $context ) { $this->instance = et_core_cache_get( $context, 'et_core_portability' ); self::$_ = ET_Core_Data_Utils::instance(); if ( $this->instance && $this->instance->view ) { if ( et_core_is_fb_enabled() ) { $this->assets(); } else { add_action( 'admin_footer', array( $this, 'modal' ) ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'modal' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'assets' ), 5 ); } } } public static function doing_import() { return self::$_doing_import; } /** * Import a previously exported layout. * * @since 3.10 Return the result of the import instead of dieing. * @since 2.7.0 * * @param string $file_context Accepts 'upload', 'sideload'. Default 'upload'. * * @return bool|array */ public function import( $file_context = 'upload' ) { global $shortname; $this->prevent_failure(); self::$_doing_import = true; $timestamp = $this->get_timestamp(); $filesystem = $this->set_filesystem(); $temp_file_id = sanitize_file_name( $timestamp ); $temp_file = $this->has_temp_file( $temp_file_id, 'et_core_import' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification is handled earlier. $include_global_presets = isset( $_POST['include_global_presets'] ) ? wp_validate_boolean( sanitize_text_field( $_POST['include_global_presets'] ) ) : false; $return_json = isset( $_POST['et_cloud_return_json'] ) ? wp_validate_boolean( sanitize_text_field( $_POST['et_cloud_return_json'] ) ) : false; $temp_presets = isset( $_POST['et_cloud_use_temp_presets'] ) ? wp_validate_boolean( sanitize_text_field( $_POST['et_cloud_use_temp_presets'] ) ) : false; $is_update_preset_id = isset( $_POST['onboarding'] ) ? wp_validate_boolean( sanitize_text_field( $_POST['onboarding'] ) ) : false; $preset_prefix = isset( $_POST['preset_prefix'] ) ? sanitize_text_field( $_POST['preset_prefix'] ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing $global_presets = ''; if ( $temp_file ) { $import = json_decode( $filesystem->get_contents( $temp_file ), true ); } else { if ( ! isset( $_FILES['file']['name'] ) || ! et_()->ends_with( sanitize_file_name( $_FILES['file']['name'] ), '.json' ) ) { return array( 'message' => 'invalideFile' ); } if ( ! in_array( $file_context, array( 'upload', 'sideload' ) ) ) { $file_context = 'upload'; } $handle_file = "wp_handle_{$file_context}"; $upload = $handle_file( $_FILES['file'], array( 'test_size' => false, 'test_type' => false, 'test_form' => false, ) ); /** * Fires before an uploaded Portability JSON file is processed. * * @since 3.0.99 * * @param string $file The absolute path to the uploaded JSON file's temporary location. */ do_action( 'et_core_portability_import_file', $upload['file'] ); $temp_file = $this->temp_file( $temp_file_id, 'et_core_import', $upload['file'] ); $file_content = preg_replace( '/\x{FEFF}/u', '', $filesystem->get_contents( $temp_file ) ); // Replace BOM with empty string. $import = json_decode( $file_content, true ); $import = $this->validate( $import ); if ( $return_json ) { return array( 'jsonFromFile' => $import ); } // Check if Import contains Google Api Settings. if ( isset( $import['data']['et_google_api_settings'] ) && ( 'epanel' === $this->instance->context || 'epanel_temp' === $this->instance->context ) ) { $et_google_api_settings = $import['data']['et_google_api_settings']; } if ( ! isset( $import['context'] ) || ( isset( $import['context'] ) && $import['context'] !== $this->instance->context ) ) { $this->delete_temp_files( 'et_core_import', [ $temp_file_id => $temp_file ] ); return array( 'message' => 'importContextFail' ); } $import['data'] = $this->apply_query( $import['data'], 'set' ); $filesystem->put_contents( $upload['file'], wp_json_encode( (array) $import ) ); } // Upload images and replace current urls. if ( isset( $import['images'] ) ) { $images = $this->maybe_paginate_images( (array) $import['images'], 'upload_images', $timestamp ); $import['data'] = $this->replace_images_urls( $images, $import['data'] ); } if ( ! empty( $import['global_colors'] ) ) { $import['data'] = $this->_maybe_inject_gcid( $import['data'], $import['global_colors'] ); } $data = $import['data']; $success = array( 'timestamp' => $timestamp ); $this->delete_temp_files( 'et_core_import', [ $temp_file_id => $temp_file ] ); if ( 'options' === $this->instance->type ) { // Reset all data besides excluded data. $current_data = $this->apply_query( get_option( $this->instance->target, array() ), 'unset' ); if ( isset( $data['wp_custom_css'] ) && function_exists( 'wp_update_custom_css_post' ) ) { wp_update_custom_css_post( $data['wp_custom_css'] ); if ( 'yes' === get_theme_mod( 'et_pb_css_synced', 'no' ) ) { // If synced, clear the legacy custom css value to avoid unwanted merging of old and new css. $data[ "{$shortname}_custom_css" ] = ''; } } // Import Google API settings. if ( isset( $et_google_api_settings ) ) { // Get exising Google API key, sine it is not added to export. $et_previous_google_api_settings = get_option( 'et_google_api_settings' ); $et_previous_google_api_key = isset( $et_previous_google_api_settings['api_key'] ) ? $et_previous_google_api_settings['api_key'] : ''; $et_google_api_settings['api_key'] = $et_previous_google_api_key; update_option( 'et_google_api_settings', $et_google_api_settings ); } // Merge remaining current data with new data and update options. update_option( $this->instance->target, array_merge( $current_data, $data ) ); set_theme_mod( 'et_pb_css_synced', 'no' ); } // Pass the post content and let js save the post. if ( 'post' === $this->instance->type ) { $success['postContent'] = reset( $data ); // In some cases we receive the post array instaed of shortcode string. Handle this case. $shortcode_string = is_array( $success['postContent'] ) && ! empty( $success['postContent']['post_content'] ) ? $success['postContent']['post_content'] : $success['postContent']; if ( ! empty( $import['presets'] ) ) { $preset_rewrite_map = []; if ( $include_global_presets && ! $is_update_preset_id ) { $preset_rewrite_map = $this->prepare_to_import_layout_presets( $import['presets'] ); $global_presets = $import['presets']; } $shortcode_object = et_fb_process_shortcode( $shortcode_string ); if ( $is_update_preset_id ) { $global_presets = $import['presets']; $vb_presets_lookup = $this->_get_importing_presets_id_name_pairs( $global_presets ); $this->_update_shortcode_with_onboarding_preset( $shortcode_object, $vb_presets_lookup ); } else { $this->rewrite_module_preset_ids( $shortcode_object, $import['presets'], $preset_rewrite_map ); } $shortcode_string = et_fb_process_to_shortcode( $shortcode_object, array(), '', false ); } do_shortcode( $shortcode_string ); $success['postContent'] = $shortcode_string; $success['migrations'] = ET_Builder_Module_Settings_Migration::$migrated; $success['presets'] = isset( $import['presets'] ) && is_array( $import['presets'] ) ? $import['presets'] : (object) array(); } if ( 'post_type' === $this->instance->type ) { $preset_rewrite_map = array(); if ( ! empty( $import['presets'] ) && $include_global_presets ) { $preset_rewrite_map = $this->prepare_to_import_layout_presets( $import['presets'] ); $global_presets = $import['presets']; } foreach ( $data as &$post ) { $shortcode_object = et_fb_process_shortcode( $post['post_content'] ); if ( ! empty( $import['presets'] ) ) { if ( $include_global_presets ) { $this->rewrite_module_preset_ids( $shortcode_object, $import['presets'], $preset_rewrite_map ); } else { $this->_apply_global_presets( $shortcode_object, $import['presets'] ); } } $post_content = et_fb_process_to_shortcode( $shortcode_object, array(), '', false ); // Add slashes for post content to avoid unwanted unslashing (by wp_unslash) while post is inserting. $post['post_content'] = wp_slash( $post_content ); // Upload thumbnail image if exist. if ( ! empty( $post['post_meta'] ) && ! empty( $post['post_meta']['_thumbnail_id'] ) ) { $post_thumbnail_origin_id = (int) $post['post_meta']['_thumbnail_id'][0]; if ( ! empty( $import['thumbnails'] ) && ! empty( $import['thumbnails'][ $post_thumbnail_origin_id ] ) ) { $post_thumbnail_new = $this->upload_images( $import['thumbnails'][ $post_thumbnail_origin_id ] ); $new_thumbnail_data = reset( $post_thumbnail_new ); // New thumbnail image was uploaded and it should be updated. if ( isset( $new_thumbnail_data['replacement_id'] ) ) { $new_thumbnail_id = $new_thumbnail_data['replacement_id']; $post['thumbnail'] = $new_thumbnail_id; if ( ! function_exists( 'wp_crop_image' ) ) { include ABSPATH . 'wp-admin/includes/image.php'; } $thumbnail_path = get_attached_file( $new_thumbnail_id ); // Generate all the image sizes and update thumbnail metadata. $new_metadata = wp_generate_attachment_metadata( $new_thumbnail_id, $thumbnail_path ); wp_update_attachment_metadata( $new_thumbnail_id, $new_metadata ); } } } } $imported_posts = $this->import_posts( $data ); if ( false === $imported_posts ) { /** * Filters the error message when {@see ET_Core_Portability::import()} fails. * * @since 3.0.99 * * @param mixed $error_message Default is `null`. */ if ( $error_message = apply_filters( 'et_core_portability_import_error_message', false ) ) { $error_message = array( 'message' => $error_message ); } return $error_message; } else { $success['imported_posts'] = $imported_posts; } } if ( ! empty( $global_presets ) ) { if ( ! $this->import_global_presets( $global_presets, $temp_presets, true, $preset_prefix, true ) ) { if ( $error_message = apply_filters( 'et_core_portability_import_error_message', false ) ) { $error_message = array( 'message' => $error_message ); } return $error_message; } } if ( ! empty( $import['global_colors'] ) ) { $this->import_global_colors( $import['global_colors'] ); $success['globalColors'] = et_builder_get_all_global_colors( true ); } return $success; } /** * Initiate Export. * * @since 2.7.0 * * @param bool $return * * @return null|array */ public function export( $return = false, $include_used_presets = false ) { $this->prevent_failure(); et_core_nonce_verified_previously(); $timestamp = $this->get_timestamp(); $filesystem = $this->set_filesystem(); $temp_file_id = sanitize_file_name( $timestamp ); $temp_file = $this->has_temp_file( $temp_file_id, 'et_core_export' ); $apply_global_presets = isset( $_POST['apply_global_presets'] ) ? wp_validate_boolean( $_POST['apply_global_presets'] ) : false; $global_presets = ''; $global_colors = ''; $thumbnails = ''; if ( $temp_file ) { $file_data = json_decode( $filesystem->get_contents( $temp_file ) ); $data = (array) $file_data->data; $global_presets = $file_data->presets; $global_colors = $file_data->global_colors; } else { $temp_file = $this->temp_file( $temp_file_id, 'et_core_export' ); if ( 'options' === $this->instance->type ) { $data = get_option( $this->instance->target, array() ); // Export the Customizer "Additional CSS" value as well. if ( function_exists( 'wp_get_custom_css' ) ) { $data[ 'wp_custom_css' ] = wp_get_custom_css(); } } if ( 'post' === $this->instance->type ) { if ( ! ( isset( $_POST['post'] ) || isset( $_POST['content'] ) ) ) { wp_send_json_error(); } $fields_validatation = array( 'ID' => 'intval', // no post_content as the default case for no fields_validation will run it through perms based wp_kses_post, which is exactly what we want. ); $post_data = array( 'post_content' => stripcslashes( $_POST['content'] ), // need to run this through stripcslashes() as thats what wp_kses_post() expects. 'ID' => $_POST['post'], ); $post_data = $this->validate( $post_data, $fields_validatation ); $data = array( $post_data['ID'] => $post_data['post_content'] ); if ( isset( $_POST['global_presets'] ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput.InputNotSanitized -- filter_post_data() function does sanitation. $post_global_presets = $this->_filter_post_data( $_POST['global_presets'] ); $global_presets = json_decode( stripslashes( $post_global_presets ) ); } if ( isset( $_POST['global_colors'] ) ) { // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput.InputNotSanitized -- filter_post_data() function does sanitation. $post_global_colors = $this->_filter_post_data( $_POST['global_colors'] ); $global_colors = json_decode( stripslashes( $post_global_colors ) ); } if ( $include_used_presets ) { $used_global_presets = array(); $used_global_colors = array(); $shortcode_object = et_fb_process_shortcode( $post_data['post_content'] ); $used_global_presets = array_merge( $this->get_used_global_presets( $shortcode_object, $used_global_presets ), $used_global_presets ); if ( ! empty( $used_global_presets ) ) { $global_presets = (object) $used_global_presets; } $used_global_colors = $this->_get_used_global_colors( $shortcode_object, $used_global_colors, $global_presets ); if ( ! empty( $used_global_colors ) ) { $global_colors = $this->_get_global_colors_data( $used_global_colors ); } } } if ( 'post_type' === $this->instance->type ) { $data = $this->export_posts_query(); } $data = $this->apply_query( $data, 'set' ); // Export Google API settings. if ( 'epanel' === $this->instance->context || 'epanel_temp' === $this->instance->context ) { $et_google_api_settings = get_option( 'et_google_api_settings', array() ); // Unset google api_key settings to prevent exporting it. if ( isset( $et_google_api_settings['api_key'] ) ) { unset( $et_google_api_settings['api_key'] ); } $data['et_google_api_settings'] = $et_google_api_settings; } if ( 'post_type' === $this->instance->type ) { $used_global_presets = array(); $used_global_colors = array(); $options = array( 'apply_global_presets' => true, ); foreach ( $data as $post ) { $shortcode_object = et_fb_process_shortcode( $post->post_content ); // We have to always process global presets to get the global colors correctly. $global_presets_from_post = $this->get_used_global_presets( $shortcode_object, $used_global_presets ); $used_global_presets = array_merge( $global_presets_from_post, $used_global_presets ); $used_global_colors = $this->_get_used_global_colors( $shortcode_object, $used_global_colors, $global_presets_from_post ); if ( $apply_global_presets ) { $shortcode_object = et_fb_process_to_shortcode( $shortcode_object, $options, '', false ); $post->post_content = $shortcode_object; } } if ( ! empty ( $used_global_presets ) ) { $global_presets = (object) $used_global_presets; } if ( ! empty( $used_global_colors ) ) { $global_colors = $this->_get_global_colors_data( $used_global_colors ); } } // put contents into file, this is temporary, // if images get paginated, this content will be brought back out // of a temp file in paginated request $file_data = array( 'data' => $data, 'presets' => $global_presets, 'global_colors' => $global_colors, ); $filesystem->put_contents( $temp_file, wp_json_encode( $file_data ) ); } $thumbnails = $this->_get_thumbnail_images( $data ); $images = $this->get_data_images( $data ); $data = array( 'context' => $this->instance->context, 'data' => $data, 'presets' => $global_presets, 'global_colors' => $global_colors, 'images' => $this->maybe_paginate_images( $images, 'encode_images', $timestamp ), 'thumbnails' => $thumbnails, ); // Return exported content instead of printing it if ( $return ) { return array_merge( $data, [ 'timestamp' => $timestamp ] ); } $filesystem->put_contents( $temp_file, wp_json_encode( (array) $data ) ); wp_send_json_success( array( 'timestamp' => $timestamp ) ); } /** * Serialize a single layout post in chunks. * * @since 4.0 * * @param integer $id Unique ID to represent this layout serialization. * @param integer $post_id * @param string $content * @param array $theme_builder_meta * @param integer $chunk * * @return array */ public function serialize_layout( $id, $post_id, $content, $theme_builder_meta = array(), $chunk = 0 ) { $this->prevent_failure(); $fields_validatation = array( // No post_content as the default case for no fields_validation will run it through perms based wp_kses_post, which is exactly what we want. 'ID' => 'intval', ); $post_data = array( // Need to run this through stripcslashes() as thats what wp_kses_post() expects. 'post_content' => stripcslashes( $content ), 'ID' => $post_id, ); $shortcode_object = et_fb_process_shortcode( $post_data['post_content'] ); $used_global_colors = $this->get_theme_builder_library_used_global_colors( $shortcode_object ); $post_data = $this->validate( $post_data, $fields_validatation ); $data = array( $post_data['ID'] => $post_data['post_content'] ); $data = $this->apply_query( $data, 'set' ); $images = $this->get_data_images( $data ); $images = $this->chunk_images( $images, 'encode_images', $id, $chunk ); // Generate list of used global colors. if ( ! empty( $used_global_colors ) ) { $global_colors = $this->_get_global_colors_data( $used_global_colors ); } $data = array( 'context' => 'et_builder', 'data' => $data, 'images' => $images['images'], 'post_title' => get_post_field( 'post_title', $post_id ), 'post_type' => get_post_type( $post_id ), 'theme_builder' => $theme_builder_meta, 'global_colors' => $global_colors, ); $chunks = $images['chunks']; $ready = $images['ready']; return array( 'ready' => $ready, 'chunks' => $chunks, 'data' => $data, ); } /** * Serialize Theme Builder templates in chunks. * * @since 4.0 * * @param integer $id Unique ID to represent this theme builder serialization process. * @param array $step * @param integer $steps * @param integer $step_index * @param integer $chunk * * @return array|false */ public function serialize_theme_builder( $id, $step, $steps, $step_index = 0, $chunk = 0 ) { if ( $step_index >= $steps ) { return false; } $this->prevent_failure(); $temp_file_id = sanitize_file_name( 'et_theme_builder_' . $id ); $temp_file = $this->has_temp_file( $temp_file_id, 'et_core_export' ); if ( $temp_file ) { $data = json_decode( $this->get_filesystem()->get_contents( $temp_file ), true ); } else { $temp_file = $this->temp_file( $temp_file_id, 'et_core_export' ); $data = array( 'context' => 'et_theme_builder', 'templates' => array(), 'layouts' => array(), 'presets' => array(), 'has_default_template' => false, 'has_global_layouts' => false, ); } $chunks = 1; switch ( $step['type'] ) { case 'template': $header_id = $step['data']['layouts']['header']['id']; $body_id = $step['data']['layouts']['body']['id']; $footer_id = $step['data']['layouts']['footer']['id']; $is_default = $step['data']['default']; if ( 0 !== $header_id && ! current_user_can( 'edit_post', $header_id ) ) { $step['data']['layouts']['header']['id'] = 0; } if ( 0 !== $body_id && ! current_user_can( 'edit_post', $body_id ) ) { $step['data']['layouts']['body']['id'] = 0; } if ( 0 !== $footer_id && ! current_user_can( 'edit_post', $footer_id ) ) { $step['data']['layouts']['footer']['id'] = 0; } if ( $is_default ) { $data['has_default_template'] = true; } $data['templates'][] = $step['data']; break; case 'layout': $post_id = $step['data']['post_id']; $is_global = $step['data']['is_global']; if ( ! current_user_can( 'edit_post', $post_id ) ) { break; } if ( 0 === $chunk && isset( $data['layouts'][ $post_id ] ) ) { // The layout is already exported. break; } if ( $is_global ) { $data['has_global_layouts'] = true; } $step_data = $this->serialize_layout( $id, $post_id, get_post_field( 'post_content', $post_id ), array( 'is_global' => $is_global, ), $chunk ); $step_data['data']['post_meta'] = array_merge( et_()->array_get( $step_data, 'data.post_meta', array() ), et_core_get_post_builder_meta( $post_id ) ); $data['layouts'][ $post_id ] = $step_data['data']; $chunks = $step_data['chunks']; break; case 'presets': $data['presets'] = $step['data']; break; } $ready = ( $step_index + 1 >= $steps ) && ( $chunk + 1 >= $chunks ); if ( ! $ready ) { $this->get_filesystem()->put_contents( $temp_file, wp_json_encode( $data ) ); } else { $this->delete_temp_files( 'et_core_export', array( $temp_file_id => $temp_file ) ); } return array( 'ready' => $ready, 'chunks' => $chunks, 'data' => $data, ); } /** * Export Theme Builder templates in chunks. * * @since 4.0 * * @param integer $id Unique ID to represent this theme builder export process. * @param array $step * @param integer $steps * @param integer $step_index * @param integer $chunk * * @return array|false */ public function export_theme_builder( $id, $step, $steps, $step_index = 0, $chunk = 0 ) { $result = $this->serialize_theme_builder( $id, $step, $steps, $step_index, $chunk ); if ( false === $result ) { return false; } $temp_file_id = sanitize_file_name( 'et_theme_builder_export_' . $id ); $temp_file = $this->temp_file( $temp_file_id, 'et_core_export' ); if ( $result['ready'] ) { $this->get_filesystem()->put_contents( $temp_file, wp_json_encode( $result[ 'data' ] ) ); } return array_merge( $result, array( 'temp_file' => $temp_file, 'temp_file_id' => $temp_file_id, ) ); } /** * Get whether an array represents a valid Theme Builder export. * * @since 4.0 * * @param array $export * * @return boolean */ public function is_valid_theme_builder_export( $export ) { $valid_context = isset( $export['context'] ) && $export['context'] === $this->instance->context; $has_templates = isset( $export['templates'] ) && is_array( $export['templates'] ); $has_layouts = isset( $export['layouts'] ) && is_array( $export['layouts'] ); return $valid_context && $has_templates && $has_layouts; } /** * Import a single layout in chunks. * * @since 4.0 * * @param string $id Unique ID to represent this layout serialization. * @param array $layout * @param integer $chunk * * @return array|false */ public function import_layout( $id, $layout, $chunk = 0 ) { $post_id = 0; $import = $this->validate( $layout ); if ( false === $import ) { return false; } $import['data'] = $this->apply_query( $import['data'], 'set' ); if ( ! isset( $import['context'] ) || ( isset( $import['context'] ) && 'et_builder' !== $import['context'] ) ) { return false; } $result = $this->chunk_images( self::$_->array_get( $import, 'images', array() ), 'upload_images', $id, $chunk ); if ( $result['ready'] ) { $import['data'] = $this->replace_images_urls( $result['images'], $import['data'] ); $post_type = self::$_->array_get( $import, 'post_type', 'post' ); $post_title = self::$_->array_get( $import, 'post_title', '' ); $post_status = self::$_->array_get( $import, 'post_status', 'publish' ); $post_meta = self::$_->array_get( $import, 'post_meta', array() ); $post_type_object = get_post_type_object( $post_type ); if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->create_posts ) ) { return false; } $content = array_values( $import['data'] ); $content = $content[0]; $args = array( 'post_status' => $post_status, 'post_type' => $post_type, 'post_content' => current_user_can( 'unfiltered_html' ) ? $content : wp_kses_post( $content ), ); if ( ! empty( $post_title ) ) { $args['post_title'] = current_user_can( 'unfiltered_html' ) ? $post_title : wp_kses( $post_title, 'entities' ); } $post_id = et_theme_builder_insert_layout( $args ); if ( is_wp_error( $post_id ) ) { return false; } foreach ( $post_meta as $entry ) { update_post_meta( $post_id, $entry['key'], $entry['value'] ); } // Import Global Colors for each layout. if ( ! empty( $import['global_colors'] ) ) { $this->import_global_colors( $import['global_colors'] ); } } return array( 'ready' => $result['ready'], 'chunks' => $result['chunks'], 'id' => $post_id, ); } /** * Get importing presets ID and name pairs from presets data. * * @since 4.26.1 * * @param array $presets Presets data. */ protected function _get_importing_presets_id_name_pairs( $presets ) { $presets_lookup = []; foreach ( $presets as $module_type => $value ) { $presets_lookup[ $module_type ] = []; foreach ( $value['presets'] as $module_preset_id => $preset ) { $presets_lookup[ $module_type ][ $module_preset_id ] = $preset['name']; } } return $presets_lookup; } /** * Import Theme Builder templates in chunks. * * @since 4.0 * * @param integer $id Unique ID to represent this theme builder import process. * @param array $step * @param integer $steps * @param integer $step_index * @param integer $chunk * * @return array|false */ public function import_theme_builder( $id, $step, $steps, $step_index = 0, $chunk = 0 ) { if ( $step_index >= $steps ) { return false; } $layout_id_map = array(); $chunks = 1; if ( ! isset( $step['type'] ) ) { $step['type'] = ''; } switch ( $step['type'] ) { case 'layout': $presets = et_()->array_get( $step, 'presets', array() ); $presets_rewrite_map = et_()->array_get( $step, 'presets_rewrite_map', array() ); $is_update_preset_id = et_()->array_get( $step, 'is_update_preset_id', false ); $import_presets = et_()->array_get( $step, 'import_presets', false ); $layouts = et_()->array_get( $step['data'], 'data', array() ); if ( $is_update_preset_id ) { $tb_presets_lookup = $this->_get_importing_presets_id_name_pairs( $presets ); } // Apply any presets to the layouts' shortcodes prior to importing them. if ( ! empty( $presets ) && ! empty( $layouts ) ) { foreach ( $layouts as $key => $layout ) { $shortcode_object = et_fb_process_shortcode( $layout ); if ( $import_presets ) { if ( $is_update_preset_id ) { $this->_update_shortcode_with_onboarding_preset( $shortcode_object, $tb_presets_lookup ); } else { $this->rewrite_module_preset_ids( $shortcode_object, $presets, $presets_rewrite_map ); } } else { $this->_apply_global_presets( $shortcode_object, $presets ); } $layouts[ $key ] = et_fb_process_to_shortcode( $shortcode_object, array(), '', false ); } $step['data']['data'] = $layouts; } $result = $this->import_layout( $id, $step['data'], $chunk ); if ( false === $result ) { break; } if ( $result['ready'] ) { if ( ! isset( $layout_id_map[ $step['id'] ] ) ) { $layout_id_map[ $step['id'] ] = array(); } // Since a single layout can be duplicated multiple times if // it's global we have to keep an array of duplicated ids. $layout_id_map[ $step['id'] ][ $step['template_id'] ] = $result['id']; } $chunks = $result['chunks']; break; } $ready = ( $step_index + 1 >= $steps ) && ( $chunk + 1 >= $chunks ); return array( 'ready' => $ready, 'chunks' => $chunks, 'layout_id_map' => $layout_id_map, ); } /** * Download temporary file. * * @since 4.0 * * @param string $filename * @param string $temp_file_id * @param string $temp_file * @return void */ public function download_file( $filename, $temp_file_id, $temp_file ) { $this->prevent_failure(); $filename = sanitize_file_name( $filename ); header( 'Content-Description: File Transfer' ); header( "Content-Disposition: attachment; filename=\"{$filename}.json\"" ); header( 'Content-Type: application/json' ); header( 'Pragma: no-cache' ); if ( file_exists( $temp_file ) ) { echo et_core_esc_previously( $this->get_filesystem()->get_contents( $temp_file ) ); } $this->delete_temp_files( 'et_core_export', array( $temp_file_id => $temp_file ) ); wp_die(); } /** * Download Export Data. * * @since 2.7.0 */ public function download_export() { $this->prevent_failure(); et_core_nonce_verified_previously(); // Retrieve data. $timestamp = isset( $_GET['timestamp'] ) ? sanitize_text_field( $_GET['timestamp'] ) : null; $name = isset( $_GET['name'] ) ? sanitize_text_field( rawurldecode( $_GET['name'] ) ) : $this->instance->name; $filesystem = $this->set_filesystem(); $temp_file = $this->temp_file( sanitize_file_name( $timestamp ), 'et_core_export' ); header( 'Content-Description: File Transfer' ); header( "Content-Disposition: attachment; filename=\"{$name}.json\"" ); header( 'Content-Type: application/json' ); header( 'Pragma: no-cache' ); if ( file_exists( $temp_file ) ) { echo et_core_esc_previously( $filesystem->get_contents( $temp_file ) ); } $this->delete_temp_files( 'et_core_export' ); exit; } protected function to_megabytes( $value ) { $unit = strtoupper( substr( $value, -1 ) ); $amount = intval( substr( $value, 0, -1 ) ); // Known units switch ( $unit ) { case 'G': return $amount << 10; case 'M': return $amount; } if ( is_numeric( $unit ) ) { // Numeric unit is present, assume bytes return intval( $value ) >> 20; } // Unknown unit ... return intval( $value ); }// end to_megabytes() /** * Get selected posts data. * * @since 2.7.0 */ protected function export_posts_query() { et_core_nonce_verified_previously(); $args = array( 'post_type' => $this->instance->target, 'posts_per_page' => -1, 'no_found_rows' => true, ); // Only include selected posts if set and not empty. if ( isset( $_POST['selection'] ) ) { $include = json_decode( stripslashes( $_POST['selection'] ), true ); if ( ! empty( $include ) ) { $include = array_map( 'intval', array_values( $include ) ); $args['post__in'] = $include; } } $get_posts = get_posts( apply_filters( "et_core_portability_export_wp_query_{$this->instance->context}", $args ) ); $taxonomies = get_object_taxonomies( $this->instance->target ); $posts = array(); foreach ( $get_posts as $key => $post ) { unset( $post->post_author, $post->guid ); $posts[$post->ID] = $post; // Include post meta. $post_meta = (array) get_post_meta( $post->ID ); if ( isset( $post_meta['_edit_lock'] ) ) { unset( $post_meta['_edit_lock'], $post_meta['_edit_last'] ); } $posts[$post->ID]->post_meta = $post_meta; // Include terms. $get_terms = (array) wp_get_object_terms( $post->ID, $taxonomies ); $terms = array(); // Order terms to make sure children are after the parents. while ( $term = array_shift( $get_terms ) ) { if ( 0 === $term->parent || isset( $terms[$term->parent] ) ) { $terms[$term->term_id] = $term; } else { // if parent category is also exporting then add the term to the end of the list and process it later // otherwise add a term as usual if ( $this->is_parent_term_included( $get_terms, $term->parent ) ) { $get_terms[] = $term; } else { $terms[$term->term_id] = $term; } } } $posts[$post->ID]->terms = array(); foreach ( $terms as $term ) { $parents_data = array(); if ( $term->parent ) { $parent_slug = isset( $terms[$term->parent] ) ? $terms[$term->parent]->slug : $this->get_parent_slug( $term->parent, $term->taxonomy ); $parents_data = $this->get_all_parents( $term->parent, $term->taxonomy ); } else { $parent_slug = 0; } $posts[$post->ID]->terms[$term->term_id] = array( 'name' => $term->name, 'slug' => $term->slug, 'taxonomy' => $term->taxonomy, 'parent' => $parent_slug, 'all_parents' => $parents_data, 'description' => $term->description ); } } return $posts; } /** * Check whether the $parent_id included into the $terms_list. * * @since 2.7.0 * * @param array $terms_list Array of term objects. * @param int $parent_id . * * @return bool */ protected function is_parent_term_included( $terms_list, $parent_id ) { $is_parent_found = false; foreach ( $terms_list as $term => $term_details ) { if ( $parent_id === $term_details->term_id ) { $is_parent_found = true; } } return $is_parent_found; } /** * Retrieve the term slug. * * @since 2.7.0 * * @param int $parent_id . * @param string $taxonomy . * * @return int|string */ protected function get_parent_slug( $parent_id, $taxonomy ) { $term_data = get_term( $parent_id, $taxonomy ); $slug = '' === $term_data->slug ? 0 : $term_data->slug; return $slug; } /** * Prepare array of all parents so the correct hierarchy can be restored during the import. * * @since 2.7.0 * * @param int $parent_id . * @param string $taxonomy . * * @return array */ protected function get_all_parents( $parent_id, $taxonomy ) { $parents_data_array = array(); $parent = $parent_id; // retrieve data for all parent categories if ( 0 !== $parent ) { while( 0 !== $parent ) { $parent_term_data = get_term( $parent, $taxonomy ); $parents_data_array[$parent_term_data->slug] = array( 'name' => $parent_term_data->name, 'description' => $parent_term_data->description, 'parent' => 0 !== $parent_term_data->parent ? $this->get_parent_slug( $parent_term_data->parent, $taxonomy ) : 0, ); $parent = $parent_term_data->parent; } } //reverse order of items, to simplify the restoring process return array_reverse( $parents_data_array ); } /** * Check if a layout exists in the database already based on both its title and its slug. * * @param string $title * @param string $slug * * @return int $post_id The post id if it exists, zero otherwise. */ protected static function layout_exists( $title, $slug ) { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_name = %s", array( wp_unslash( sanitize_post_field( 'post_title', $title, 0, 'db' ) ), wp_unslash( sanitize_post_field( 'post_name', $slug, 0, 'db' ) ), ) ) ); } /** * Update shortcode with onboarding preset. * * @since 4.26.1 * * @param array $shortcode_object - The shortcode object to be updated. * @param array $presets_lookup - The lookup table for presets. */ protected function _update_shortcode_with_onboarding_preset( &$shortcode_object, $presets_lookup ) { $global_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $module_preset_attribute = ET_Builder_Global_Presets_Settings::MODULE_PRESET_ATTRIBUTE; $global_presets = $global_presets_manager->get_global_presets(); foreach ( $shortcode_object as &$module ) { $module_type = $global_presets_manager->maybe_convert_module_type( $module['type'], $module['attrs'] ); if ( isset( $presets_lookup[ $module_type ] ) ) { $module_preset_id = et_()->array_get( $module, "attrs.{$module_preset_attribute}", 'default' ); if ( 'default' === $module_preset_id ) { continue; } $module_preset_name = et_()->array_get( $presets_lookup, "{$module_type}.{$module_preset_id}", '' ); foreach ( $global_presets->$module_type->presets as $preset_id => $preset ) { if ( $preset->name === $module_preset_name ) { $module['attrs'][ $module_preset_attribute ] = $preset_id; break; } } } if ( isset( $module['content'] ) && is_array( $module['content'] ) ) { $this->_update_shortcode_with_onboarding_preset( $module['content'], $presets_lookup ); } } } /** * Imports Global Presets * * @since 4.0.10 Made public. * * @param array $presets - The Global Presets to be imported. * @param bool $is_temp_presets - Whether the presets are temporary or not. * @param bool $override_defaults - Whether the default presets should be overridden. * @param string $preset_prefix - A prefix to be prepended to preset name of the preset being imported, if override_defaults is true. * @param bool $skip_default - Whether updating the default preset key should be skipped. * * @return boolean */ public function import_global_presets( $presets, $is_temp_presets = false, $override_defaults = false, $preset_prefix = '', $skip_default = false ) { if ( ! is_array( $presets ) ) { return false; } $all_modules = ET_Builder_Element::get_modules(); $module_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $global_presets = $module_presets_manager->get_global_presets(); $temp_presets = $module_presets_manager->get_temp_presets(); $presets_to_import = array(); foreach ( $presets as $module_type => $module_presets ) { $presets_to_import[ $module_type ] = array( 'presets' => array(), ); if ( ! isset( $global_presets->$module_type->presets ) ) { $initial_preset_structure = ET_Builder_Global_Presets_Settings::generate_module_initial_presets_structure( $module_type, $all_modules ); $global_presets->$module_type = $initial_preset_structure; } if ( $override_defaults && ! $skip_default ) { $global_presets->$module_type->default = $module_presets['default']; } $local_presets = $global_presets->$module_type->presets; $local_preset_names = array(); foreach ( $local_presets as $preset_id => $preset ) { // Skip temp presets. if ( ! isset( $temp_preset[ $module_type ]['presets'][ $preset_id ] ) ) { array_push( $local_preset_names, $preset->name ); } } foreach ( $module_presets['presets'] as $preset_id => $preset ) { $name = sanitize_text_field( $preset['name'] ); if ( $override_defaults && $preset_prefix ) { $name = $preset_prefix . ' ' . $name; // No duplicates allowed on override. if ( in_array( $name, $local_preset_names, true ) ) { continue; } } else { if ( in_array( $name, $local_preset_names, true ) ) { $name .= ' ' . esc_html__( 'imported', 'et-core' ); } } $presets_to_import[ $module_type ]['presets'][ $preset_id ] = array( 'name' => $name, 'created' => time() * 1000, 'updated' => time() * 1000, 'version' => $preset['version'], 'settings' => $preset['settings'], ); } } if ( $is_temp_presets ) { et_update_option( ET_Builder_Global_Presets_Settings::GLOBAL_PRESETS_OPTION_TEMP, $presets_to_import ); } // Merge existing Global Presets with imported ones foreach ( $presets_to_import as $module_type => $module_presets ) { foreach ( $module_presets['presets'] as $preset_id => $preset ) { $global_presets->$module_type->presets->$preset_id = (object) array(); $global_presets->$module_type->presets->$preset_id->name = sanitize_text_field( $preset['name'] ); $global_presets->$module_type->presets->$preset_id->created = $preset['created']; $global_presets->$module_type->presets->$preset_id->updated = $preset['updated']; $global_presets->$module_type->presets->$preset_id->version = $preset['version']; $global_presets->$module_type->presets->$preset_id->settings = (object) array(); foreach ( $preset['settings'] as $setting_name => $value ) { $setting_name_sanitized = sanitize_text_field( $setting_name ); $value_sanitized = sanitize_text_field( $value ); $global_presets->$module_type->presets->$preset_id->settings->$setting_name_sanitized = $value_sanitized; } // Inject Global colors into imported presets. $preset_settings = (array) $global_presets->$module_type->presets->$preset_id->settings; $global_presets->$module_type->presets->$preset_id->settings = ET_Builder_Global_Presets_Settings::maybe_set_global_colors( $preset_settings ); } } // Update option for product setting (last attr in args list). et_update_option( ET_Builder_Global_Presets_Settings::GLOBAL_PRESETS_OPTION, $global_presets, false, '', '', true ); if ( ! $is_temp_presets ) { $global_presets_history = ET_Builder_Global_Presets_History::instance(); $global_presets_history->add_global_history_record( $global_presets ); } return true; } /** * Prepare to import non-duplicate presets. * * @since 4.26.0 * * @param array $presets Presets to import. * * @return array */ public function prepare_to_import_non_duplicate_presets( $presets ) { $global_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $existing_presets = $global_presets_manager->get_global_presets(); $existing_names = []; foreach ( $existing_presets as $module_slug => $data ) { foreach ( $data->presets as $preset ) { $existing_names[ $module_slug ][ trim( $preset->name ) ] = true; } } foreach ( $presets as $module_slug => $data ) { foreach ( $data['presets'] as $preset_id => $preset ) { if ( isset( $existing_names[ $module_slug ][ trim( $preset['name'] ) ] ) ) { unset( $presets[ $module_slug ]['presets'][ $preset_id ] ); } } } return $presets; } /** * Import global colors. * * @since 4.9.0 * * @param array $incoming_global_colors Global Colors Array. * * @return void */ public function import_global_colors( $incoming_global_colors ) { $excluded_colors = array( 'gcid-primary-color', 'gcid-secondary-color', 'gcid-heading-color', 'gcid-body-color' ); $global_colors = array(); foreach ( $incoming_global_colors as $incoming_gcolor ) { $key = et_()->sanitize_text_fields( $incoming_gcolor[0] ); // Skip excluded colors. if ( in_array( $key, $excluded_colors, true ) ) { continue; } $global_colors[ $key ] = et_()->sanitize_text_fields( $incoming_gcolor[1] ); } $stored_global_colors = et_builder_get_all_global_colors(); if ( ! empty( $stored_global_colors ) ) { $global_colors = array_merge( $global_colors, $stored_global_colors ); } et_update_option( 'et_global_colors', $global_colors ); } /** * Import post. * * @since 2.7.0 * * @param array $posts Array of data formatted by the portability exporter. * * @return bool */ protected function import_posts( $posts ) { /** * Filters the array of builder layouts to import. Returning an empty value will * short-circuit the import process. * * @since 3.0.99 * * @param array $posts */ $posts = apply_filters( 'et_core_portability_import_posts', $posts ); $imported_posts = array(); if ( empty( $posts ) ) { return false; } foreach ( $posts as $post ) { if ( isset( $post['post_status'] ) && 'auto-draft' === $post['post_status'] ) { continue; } $fields_validatation = array( 'ID' => 'intval', 'post_title' => 'sanitize_text_field', 'post_type' => 'sanitize_text_field', ); if ( ! $post = $this->validate( $post, $fields_validatation ) ) { continue; } $layout_exists = self::layout_exists( $post['post_title'], $post['post_name'] ); if ( $layout_exists && get_post_type( $layout_exists ) === $post['post_type'] ) { // Make sure the post is published. if ( 'publish' !== get_post_status( $layout_exists ) ) { wp_update_post( array( 'ID' => intval( $layout_exists ), 'post_status' => 'publish', ) ); } $imported_posts[] = intval( $layout_exists ); continue; } $post['import_id'] = $post['ID']; unset( $post['ID'] ); $post['post_author'] = (int) get_current_user_id(); // Insert or update post. $post_id = wp_insert_post( $post, true ); if ( ! $post_id || is_wp_error( $post_id ) ) { continue; } $imported_posts[] = $post_id; // Insert and set terms. if ( isset( $post['terms'] ) && is_array( $post['terms'] ) ) { $processed_terms = array(); foreach ( $post['terms'] as $term ) { $fields_validatation = array( 'name' => 'sanitize_text_field', 'slug' => 'sanitize_title', 'taxonomy' => 'sanitize_title', 'parent' => 'sanitize_title', 'description' => 'wp_kses_post', ); if ( ! $term = $this->validate( $term, $fields_validatation ) ) { continue; } if ( empty( $term['parent'] ) ) { $parent = 0; } else { if ( isset( $term['all_parents'] ) && ! empty( $term['all_parents'] ) ) { $this->restore_parent_categories( $term['all_parents'], $term['taxonomy'] ); } $parent = term_exists( $term['parent'], $term['taxonomy'] ); if ( is_array( $parent ) ){ $parent = $parent['term_id']; } } if ( ! $insert = term_exists( $term['slug'], $term['taxonomy'] ) ) { $insert = wp_insert_term( $term['name'], $term['taxonomy'], array( 'slug' => $term['slug'], 'description' => $term['description'], 'parent' => intval( $parent ), ) ); } if ( is_array( $insert ) && ! is_wp_error( $insert ) ) { $processed_terms[$term['taxonomy']][] = $term['slug']; } } // Set post terms. foreach ( $processed_terms as $taxonomy => $ids ) { wp_set_object_terms( $post_id, $ids, $taxonomy ); } } // Insert or update post meta. if ( isset( $post['post_meta'] ) && is_array( $post['post_meta'] ) ) { foreach ( $post['post_meta'] as $meta_key => $meta ) { $meta_key = sanitize_text_field( $meta_key ); if ( count( $meta ) < 2 ) { $meta = wp_kses_post( $meta[0] ); } else { $meta = array_map( 'wp_kses_post', $meta ); } update_post_meta( $post_id, $meta_key, $meta ); } } // Assign new thumbnail if provided. if ( isset( $post['thumbnail'] ) ) { set_post_thumbnail( $post_id, $post['thumbnail'] ); } } return $imported_posts; } /** * Restore the categories hierarchy in library. * * @since 2.7.0 * * @param array $parents_array Array of parent categories data. * @param string $taxonomy */ protected function restore_parent_categories( $parents_array, $taxonomy ) { foreach( $parents_array as $slug => $category_data ) { $current_category = term_exists( $slug, $taxonomy ); if ( ! is_array( $current_category ) ) { $parent_id = 0 !== $category_data['parent'] ? term_exists( $category_data['parent'], $taxonomy ) : 0; wp_insert_term( $category_data['name'], $taxonomy, array( 'slug' => $slug, 'description' => $category_data['description'], 'parent' => is_array( $parent_id ) ? $parent_id['term_id'] : $parent_id, ) ); } else if ( ( ! isset( $current_category['parent'] ) || 0 === $current_category['parent'] ) && 0 !== $category_data['parent'] ) { $parent_id = 0 !== $category_data['parent'] ? term_exists( $category_data['parent'], $taxonomy ) : 0; wp_update_term( $current_category['term_id'], $taxonomy, array( 'parent' => is_array( $parent_id ) ? $parent_id['term_id'] : $parent_id ) ); } } } /** * Generates UUIDs for the presets to avoid collisions. * * @since 4.5.0 * * @param array $global_presets - The Global Presets to be imported * * @return array - The list of module types for which preset ids have been changed */ public function prepare_to_import_layout_presets( &$global_presets ) { $preset_rewrite_map = array(); $initial_preset_id = ET_Builder_Global_Presets_Settings::MODULE_INITIAL_PRESET_ID; foreach ( $global_presets as $component_type => &$component_presets ) { $preset_rewrite_map[ $component_type ] = array(); foreach ( $component_presets['presets'] as $preset_id => $preset ) { $new_id = ET_Core_Data_Utils::uuid_v4(); $component_presets['presets'][ $new_id ] = $preset; $preset_rewrite_map[ $component_type ][ $preset_id ] = $new_id; unset( $component_presets['presets'][ $preset_id ] ); } if ( $component_presets['default'] === $initial_preset_id && ! isset( $preset_rewrite_map[ $component_type ][ $initial_preset_id ] ) ) { $new_id = ET_Core_Data_Utils::uuid_v4(); $component_presets['default'] = $new_id; if ( isset( $component_presets['presets'][ $initial_preset_id ] ) ) { $component_presets['presets'][ $new_id ] = $component_presets['presets'][ $initial_preset_id ]; unset( $component_presets['presets'][ $initial_preset_id ] ); } $preset_rewrite_map[ $component_type ][ $initial_preset_id ] = $new_id; } else { $component_presets['default'] = $preset_rewrite_map[ $component_type ][ $component_presets['default'] ]; } } return $preset_rewrite_map; } /** * Injects the given Global Presets settings into the imported layout * * @since 4.5.0 * * @param array $shortcode_object - The multidimensional array representing a page/module structure * @param array $global_presets - The Global Presets to be imported * @param array $preset_rewrite_map - The list of module types for which preset ids have been changed */ protected function rewrite_module_preset_ids( &$shortcode_object, $global_presets, $preset_rewrite_map ) { $global_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $module_preset_attribute = ET_Builder_Global_Presets_Settings::MODULE_PRESET_ATTRIBUTE; foreach ( $shortcode_object as &$module ) { $module_type = $global_presets_manager->maybe_convert_module_type( $module['type'], $module['attrs'] ); $module_preset_id = et_()->array_get( $module, "attrs.{$module_preset_attribute}", 'default' ); if ( $module_preset_id === 'default' ) { $module['attrs'][ $module_preset_attribute ] = et_()->array_get( $global_presets, "{$module_type}.default", 'default' ); } else { if ( isset( $preset_rewrite_map[ $module_type ][ $module_preset_id ] ) ) { $module['attrs'][ $module_preset_attribute ] = $preset_rewrite_map[ $module_type ][ $module_preset_id ]; } else { $module['attrs'][ $module_preset_attribute ] = et_()->array_get( $global_presets, "{$module_type}.default", 'default' ); } } if ( isset( $module['content'] ) && is_array( $module['content'] ) ) { $this->rewrite_module_preset_ids( $module['content'], $global_presets, $preset_rewrite_map ); } } } /** * Injects global color ids into the imported layout * * @since 4.10.0 * * @param array $data - The multidimensional array representing a import object structure. */ protected function _maybe_inject_gcid( &$data, &$gcolors = null ) { foreach ( $data as $post_id => &$post_data ) { if ( is_array( $post_data ) ) { $shortcode_object = et_fb_process_shortcode( $post_data['post_content'] ); $this->_inject_gcid( $shortcode_object, $gcolors ); $data[ $post_id ]['post_content'] = et_fb_process_to_shortcode( $shortcode_object, array(), '', false ); } else { $shortcode_object = et_fb_process_shortcode( $post_data ); $this->_inject_gcid( $shortcode_object, $gcolors ); $data[ $post_id ] = et_fb_process_to_shortcode( $shortcode_object, array(), '', false ); } } unset( $post_data ); return $data; } /** * Process and inject global color ids into the shortcode * * @since 4.10.0 * * @param array $shortcode_object - The multidimensional array representing a page/module structure. */ protected function _inject_gcid( &$shortcode_object, &$global_colors ) { foreach ( $shortcode_object as &$module ) { // No global colors set for this module. if ( ! empty( $module['attrs']['global_colors_info'] ) && ! empty( $global_colors ) ) { $colors_array = json_decode( $module['attrs']['global_colors_info'], true ); if ( ! empty( $colors_array ) ) { foreach ( $colors_array as $color_id => $attrs_array ) { if ( ! empty( $attrs_array ) ) { // Get settings for this global color. $color = ''; foreach ( $global_colors as $gcid ) { if ( $color_id === $gcid[0] && 'yes' === $gcid[1]['active'] ) { $color = $gcid[1]['color']; } } foreach ( $attrs_array as $attr_name ) { if ( isset( $module['attrs'][ $attr_name ] ) && '' !== $module['attrs'][ $attr_name ] ) { // Match substring (needed for attrs like gradient stops). $module['attrs'][ $attr_name ] = str_replace( $color, $color_id, $module['attrs'][ $attr_name ] ); } } } } } } if ( isset( $module['content'] ) && is_array( $module['content'] ) ) { $this->_inject_gcid( $module['content'], $global_colors ); } } } /** * Injects the given Global Presets settings into the imported layout * * @since 3.26 * * @param array $shortcode_object - The multidimensional array representing a page/module structure * @param array $global_presets - The Global Presets to be applied */ protected function _apply_global_presets( &$shortcode_object, $global_presets ) { $global_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $module_preset_attribute = ET_Builder_Global_Presets_Settings::MODULE_PRESET_ATTRIBUTE; foreach ( $shortcode_object as &$module ) { $module_type = $global_presets_manager->maybe_convert_module_type( $module['type'], $module['attrs'] ); if ( isset( $global_presets[ $module_type ] ) ) { $default_preset_id = et_()->array_get( $global_presets, "{$module_type}.default", null ); $module_preset_id = et_()->array_get( $module, "attrs.{$module_preset_attribute}", $default_preset_id ); if ( 'default' === $module_preset_id ) { $module_preset_id = $default_preset_id; } $preset_settings = array(); if ( isset( $global_presets[ $module_type ]['presets'][ $module_preset_id ] ) ) { $preset_settings = $global_presets[ $module_type ]['presets'][ $module_preset_id ]['settings']; } else { if ( isset( $global_presets[ $module_type ]['presets'][ $default_preset_id ]['settings'] ) ) { $preset_settings = $global_presets[ $module_type ]['presets'][ $default_preset_id ]['settings']; } } $merged_global_colors_info = array(); if ( isset( $module['attrs']['global_colors_info'] ) ) { // Retrive global_colors_info from post meta, which saved as string[][]. $gc_info_prepared = str_replace( array( '[', ']' ), array( '[', ']' ), $module['attrs']['global_colors_info'] ); $used_global_colors = json_decode( $gc_info_prepared, true ); $merged_global_colors_info = $used_global_colors; } // Merge Global Colors from preset. if ( isset( $preset_settings['global_colors_info'] ) ) { $preset_global_colors = json_decode( $preset_settings['global_colors_info'], true ); if ( ! empty( $preset_global_colors ) ) { foreach ( $preset_global_colors as $color_id => $settings_list ) { if ( ! empty( $settings_list ) ) { if ( isset( $used_global_colors[ $color_id ] ) ) { $merged_global_colors_info[ $color_id ] = array_merge( $used_global_colors[ $color_id ], $settings_list ); } else { $merged_global_colors_info[ $color_id ] = $settings_list; } foreach ( $settings_list as $setting_name ) { $preset_settings[ $setting_name ] = $color_id; } } } } } $module['attrs'] = array_merge( $preset_settings, $module['attrs'] ); $module['attrs']['global_colors_info'] = wp_json_encode( $merged_global_colors_info ); } if ( isset( $module['content'] ) && is_array( $module['content'] ) ) { $this->_apply_global_presets( $module['content'], $global_presets ); } } } /** * Restrict data according the argument registered. * * @since 2.7.0 * * @param array $data Array of data the query is applied on. * @param string $method Whether data should be set or reset. Accepts 'set' or 'unset' which is * should be used when treating existing data in the db. * * @return array */ protected function apply_query( $data, $method ) { $operator = ( $method === 'set' ) ? true : false; foreach ( $data as $id => $value ) { if ( ! empty( $this->instance->exclude ) && isset( $this->instance->exclude[$id] ) === $operator ) { unset( $data[$id] ); } if ( ! empty( $this->instance->include ) && isset( $this->instance->include[$id] ) === ! $operator ) { unset( $data[$id] ); } } return $data; } /** * Serialize images in chunks. * * @since 4.0 * * @param array $images * @param string $method Method applied on images. * @param string $id Unique ID to use for temporary files. * @param integer $chunk * * @return array */ protected function chunk_images( $images, $method, $id, $chunk = 0 ) { $images_per_chunk = 5; $chunks = 1; /** * Filters whether or not images in the file being imported should be paginated. * * @since 3.0.99 * * @param bool $paginate_images Default `true`. */ $paginate_images = apply_filters( 'et_core_portability_paginate_images', true ); if ( $paginate_images && count( $images ) > $images_per_chunk ) { $chunks = ceil( count( $images ) / $images_per_chunk ); $slice = $images_per_chunk * $chunk; $images = array_slice( $images, $slice, $images_per_chunk ); $images = $this->$method( $images ); $filesystem = $this->get_filesystem(); $temp_file_id = sanitize_file_name( "images_{$id}" ); $temp_file = $this->temp_file( $temp_file_id, 'et_core_export' ); $temp_images = json_decode( $filesystem->get_contents( $temp_file ), true ); if ( is_array( $temp_images ) ) { $images = array_merge( $temp_images, $images ); } if ( $chunk + 1 < $chunks ) { $filesystem->put_contents( $temp_file, wp_json_encode( (array) $images ) ); } else { $this->delete_temp_files( 'et_core_export', array( $temp_file_id => $temp_file ) ); } } else { $images = $this->$method( $images ); } return array( 'ready' => $chunk + 1 >= $chunks, 'chunks' => $chunks, 'images' => $images, ); } /** * Paginate images processing. * * @since 1.0.0 * * @param $images * @param string $method Method applied on images. * @param int $timestamp Timestamp used to store data upon pagination. * * @return array * @internal param array $data Array of images. */ protected function maybe_paginate_images( $images, $method, $timestamp ) { et_core_nonce_verified_previously(); $page = isset( $_POST['page'] ) ? (int) $_POST['page'] : 1; $result = $this->chunk_images( $images, $method, $timestamp, max( $page - 1, 0 ) ); if ( ! $result['ready'] ) { wp_send_json( array( 'page' => $page, 'total_pages' => $result['chunks'], 'timestamp' => $timestamp, ) ); } return $result['images']; } /** * Get all thumbnail images in the data given. * * @since 4.7.4 * * @param array $data Array of data. * * @return array */ protected function _get_thumbnail_images( $data ) { $thumbnails = array(); foreach ( $data as $post_data ) { // If post has thumbnail. if ( ! empty( $post_data->post_meta ) && ! empty( $post_data->post_meta->_thumbnail_id ) ) { $post_thumbnail = get_the_post_thumbnail_url( $post_data->ID ); // If thumbnail image found in the WP Media library. if ( $post_thumbnail ) { $thumbnail_id = (int) $post_data->post_meta->_thumbnail_id[0]; $thumbnail_image = $this->encode_images( array( $thumbnail_id ) ); $thumbnails[ $thumbnail_id ] = $thumbnail_image; } } } return $thumbnails; } /** * Get all images in the data given. * * @since 2.7.0 * * @param array $data Array of data. * @param bool $force Set whether the value should be added by force. Usually used for image ids. * * @return array */ protected function get_data_images( $data, $force = false ) { if ( empty( $data ) ) { return array(); } $images = array(); $images_src = array(); $basenames = array( 'src', 'image_url', 'background_image', 'image', 'url', 'bg_img_?\d?', ); $suffixes = array( '__hover', '_tablet', '_phone' ); foreach ( $basenames as $basename ) { $images_src[] = $basename; foreach ( $suffixes as $suffix ) { $images_src[] = $basename . $suffix; } } foreach ( $data as $value ) { // If the $value is an object and there is no post_content property, // it's unlikely to contain any image data so we can continue with the next iteration. if ( is_object( $value ) && ! property_exists( $value, 'post_content' ) ) { continue; } if ( is_array( $value ) || is_object( $value ) ) { // If the $value contains the post_content property, set $value to use // this object's property value instead of the entire object. if ( is_object( $value ) && property_exists( $value, 'post_content' ) ) { $value = $value->post_content; } $images = array_merge( $images, $this->get_data_images( (array) $value ) ); continue; } // Extract images from HTML or shortcodes. if ( preg_match_all( '/(' . implode( '|', $images_src ) . ')="(?P<src>\w+[^"]*)"/i', $value, $matches ) ) { foreach ( array_unique( $matches['src'] ) as $key => $src ) { $images = array_merge( $images, $this->get_data_images( array( $key => $src ) ) ); } } // Extract images from shortcodes gallery. if ( preg_match_all( '/gallery_ids="(?P<ids>\w+[^"]*)"/i', $value, $matches ) ) { foreach ( array_unique( $matches['ids'] ) as $galleries ) { $explode = explode( ',', str_replace( ' ', '', $galleries ) ); foreach ( $explode as $image_id ) { $images = array_merge( $images, $this->get_data_images( array( (int) $image_id ), true ) ); } } } if ( preg_match( '/^.+?\.(jpg|jpeg|jpe|png|gif|svg|webp)/', $value, $match ) || $force ) { $basename = basename( $value ); // Skip if the value is not a valid URL or an image ID (integer). if ( ! ( wp_http_validate_url( $value ) || is_int( $value ) ) ) { continue; } // Skip if the images array already contains the value to avoid duplicates. if ( isset( $images[$value] ) ) { continue; } $images[$value] = $value; } } return $images; } /** * Get the attachment post id for the given url. * * @since 3.22.3 * * @param string $url The url of an attachment file. * * @return int */ protected function _get_attachment_id_by_url( $url ) { global $wpdb; // Remove any thumbnail size suffix from the filename and use that as a fallback. $fallback_url = preg_replace( '/-\d+x\d+(\.[^.]+)$/i', '$1', $url ); // Scenario: Trying to find the attachment for a file called x-150x150.jpg. // 1. Since WordPress adds the -150x150 suffix for thumbnail sizes we cannot be // sure if this is an attachment or an attachment's generated thumbnail. // 2. Since both x.jpg and x-150x150.jpg can be uploaded as separate attachments // we must decide which is a better match. // 3. The above is why we order by guid length and use the first result. $attachments_query = $wpdb->prepare( " SELECT id FROM $wpdb->posts WHERE `post_type` = %s AND `guid` IN ( %s, %s ) ORDER BY CHAR_LENGTH( `guid` ) DESC ", 'attachment', esc_url_raw( $url ), esc_url_raw( $fallback_url ) ); $attachment_id = (int) $wpdb->get_var( $attachments_query ); return $attachment_id; } /** * Encode image in a base64 format. * * @since 2.7.0 * * @param array $images Array of data for which images need to be encoded if any. * * @return array */ protected function encode_images( $images ) { $encoded = array(); foreach ( $images as $url ) { $id = 0; $image = ''; if ( is_int( $url ) ) { $id = $url; $url = wp_get_attachment_url( $id ); } else { $id = $this->_get_attachment_id_by_url( $url ); } if ( $id > 0 ) { $image = $this->_encode_attachment_image( $id ); } if ( empty( $image ) ) { // Case 1: No attachment found. // Case 2: Attachment found, but file does not exist (may be stored on a CDN, for example). $image = $this->_encode_remote_image( $url ); } if ( empty( $image ) ) { // All fetching methods have failed - bail on encoding. continue; } $encoded[ $url ] = array( 'encoded' => $image, 'url' => $url, ); // Add image id for replacement purposes. if ( $id > 0 ) { $encoded[ $url ]['id'] = $id; } } return $encoded; } /** * Encode an image attachment. * * @since 3.22.3 * * @param int $id * * @return string */ protected function _encode_attachment_image( $id ) { /** * @var WP_Filesystem_Base $wp_filesystem */ global $wp_filesystem; if ( ! current_user_can( 'read_post', $id ) ) { return ''; } $file = get_attached_file( $id ); if ( ! $wp_filesystem->exists( $file ) ) { return ''; } $image = $wp_filesystem->get_contents( $file ); if ( empty( $image ) ) { return ''; } return base64_encode( $image ); } /** * Encode a remote image. * * @since 3.22.3 * * @param string $url * * @return string */ protected function _encode_remote_image( $url ) { $request = wp_remote_get( esc_url_raw( $url ), array( 'timeout' => 2, 'redirection' => 2, ) ); if ( ! is_array( $request ) || is_wp_error( $request ) ) { return ''; } if ( ! self::$_->includes( $request['headers']['content-type'], 'image' ) ) { return ''; } $image = wp_remote_retrieve_body( $request ); if ( ! $image ) { return ''; } return base64_encode( $image ); } /** * Decode base64 formatted image and upload it to WP media. * * @since 2.7.0 * * @param array $images Array of encoded images which needs to be uploaded. * * @return array */ protected function upload_images( $images ) { $filesystem = $this->set_filesystem(); /** * Filters whether or not to allow duplicate images to be uploaded * during Portability import. * * @since 4.14.8 * * @param bool $allow_duplicates Whether or not to allow duplicates. Default is `false`. */ $allow_duplicates = apply_filters( 'et_core_portability_import_attachment_allow_duplicates', false ); foreach ( $images as $key => $image ) { $basename = sanitize_file_name( wp_basename( $image['url'] ) ); $id = 0; $url = ''; if ( ! $allow_duplicates ) { $attachments = get_posts( array( 'posts_per_page' => -1, 'post_type' => 'attachment', 'meta_key' => '_wp_attached_file', 'meta_value' => pathinfo( $basename, PATHINFO_FILENAME ), 'meta_compare' => 'LIKE', ) ); // Avoid duplicates. if ( ! is_wp_error( $attachments ) && ! empty( $attachments ) ) { foreach ( $attachments as $attachment ) { $attachment_url = wp_get_attachment_url( $attachment->ID ); $file = get_attached_file( $attachment->ID ); $filename = sanitize_file_name( wp_basename( $file ) ); // Use existing image only if the content matches. if ( $filesystem->get_contents( $file ) === base64_decode( $image['encoded'] ) ) { $id = isset( $image['id'] ) ? $attachment->ID : 0; $url = $attachment_url; break; } } } } // Create new image. if ( empty( $url ) ) { $temp_file = wp_tempnam(); $filesystem->put_contents( $temp_file, base64_decode( $image['encoded'] ) ); $filetype = wp_check_filetype_and_ext( $temp_file, $basename ); if ( ! $allow_duplicates && ! empty( $attachments ) && ! is_wp_error( $attachments ) ) { // Avoid further duplicates if the proper_filename matches an existing image. if ( isset( $filetype['proper_filename'] ) && $filetype['proper_filename'] !== $basename ) { foreach ( $attachments as $attachment ) { $attachment_url = wp_get_attachment_url( $attachment->ID ); $file = get_attached_file( $attachment->ID ); $filename = sanitize_file_name( wp_basename( $file ) ); if ( isset( $filename ) && $filename === $filetype['proper_filename'] ) { // Use existing image only if the basenames and content match. if ( $filesystem->get_contents( $file ) === $filesystem->get_contents( $temp_file ) ) { $id = isset( $image['id'] ) ? $attachment->ID : 0; $url = $attachment_url; $filesystem->delete( $temp_file ); break; } } } } } $file = array( 'name' => $basename, 'tmp_name' => $temp_file, ); $upload = media_handle_sideload( $file ); $attachment_id = is_wp_error( $upload ) ? 0 : $upload; /** * Fires when image attachments are created during portability import. * * @since 4.14.6 * * @param int $attachment_id The attachment id or 0 if attachment upload failed. */ do_action( 'et_core_portability_import_attachment_created', $attachment_id ); if ( ! is_wp_error( $upload ) ) { // Set the replacement as an id if the original image was set as an id (for gallery). $id = isset( $image['id'] ) ? $upload : 0; $url = wp_get_attachment_url( $upload ); } else { // Make sure the temporary file is removed if media_handle_sideload didn't take care of it. $filesystem->delete( $temp_file ); } } // Only declare the replace if a url is set. if ( $id > 0 ) { $images[$key]['replacement_id'] = $id; } if ( ! empty( $url ) ) { $images[$key]['replacement_url'] = $url; } unset( $url ); } return $images; } /** * Replace encoded image url with a real url * * @param $subject - The string to perform replacing for * @param array $image - The image settings * * @return string|string[]|null */ protected function replace_image_url( $subject, $image ) { if ( isset( $image['replacement_id'] ) && isset( $image['id'] ) ) { $search = $image['id']; $replacement = $image['replacement_id']; $subject = preg_replace( "/(gallery_ids=.*?){$search}(.*?)/", "\${1}{$replacement}\${2}", $subject ); } if ( isset( $image['url'] ) && isset( $image['replacement_url'] ) && $image['url'] !== $image['replacement_url'] ) { $search = $image['url']; $replacement = $image['replacement_url']; $subject = str_replace( $search, $replacement, $subject ); } return $subject; } /** * Replace image urls with newly uploaded images. * * @since 2.7.0 * * @param array $images Array of new images uploaded. * @param array $data Array of for which images url needs to be replaced. * * @return array|mixed|object */ protected function replace_images_urls( $images, $data ) { foreach ( $data as $post_id => &$post_data ) { foreach ( $images as $image ) { if ( is_array( $post_data ) ) { foreach ( $post_data as $post_param => &$param_value ) { if ( ! is_array( $param_value ) ) { $data[ $post_id ][ $post_param ] = $this->replace_image_url( $param_value, $image ); } } unset($param_value); } else { $data[ $post_id ] = $this->replace_image_url( $post_data, $image ); } } } unset($post_data); return $data; } /** * Validate data and remove any malicious code. * * @since 2.7.0 * * @param array $data Array of data which needs to be validated. * @param array $fields_validation Array of field and validation callback. * * @return array|bool */ protected function validate( $data, $fields_validation = array() ) { if ( ! is_array( $data ) ) { return false; } foreach ( $data as $key => $value ) { if ( empty( $value ) ) { continue; } if ( is_array( $value ) ) { $data[$key] = $this->validate( $value, $fields_validation ); } else { if ( isset( $fields_validation[$key] ) ) { // @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found $data[$key] = call_user_func( $fields_validation[$key], $value ); } else { if ( current_user_can( 'unfiltered_html' ) ) { $data[ $key ] = $value; } else { $data[ $key ] = wp_kses_post( $value ); } } } } return $data; } /** * Filters a variable with string filter * * @param mixed $data - Value to filter. * * @return mixed */ protected function _filter_post_data( $data ) { return filter_var( $data, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES ); } /** * Prevent import and export timeout or memory failure. * * @since 2.7.0 * * It doesn't need to be reset as in both case the request exit. */ protected function prevent_failure() { @set_time_limit( 0 ); // Increase memory which is safe at this stage of the request. if ( et_core_get_memory_limit() < 256 ) { @ini_set( 'memory_limit', '256M' ); } } /** * Set WP filesystem to direct. This should only be use to create a temporary file. * * @since 2.7.0 * * It is safe to do so since the created file is removed immediately after import. The method does'nt have * to be reset since the ajax query is exited. */ protected function set_filesystem() { global $wp_filesystem; add_filter( 'filesystem_method', array( $this, 'replace_filesystem_method' ) ); WP_Filesystem(); return $wp_filesystem; } /** * Proxy method for set_filesystem() to avoid calling it multiple times. * * @since 4.0 * * @return WP_Filesystem_Direct */ protected function get_filesystem() { static $filesystem = null; if ( null === $filesystem ) { $filesystem = $this->set_filesystem(); } return $filesystem; } /** * Check if a temporary file is register. Returns temporary file if it exists. * * @since 4.0 Made method public. * * @param string $id Unique id used when the temporary file was created. * @param string $group Group name in which files are grouped. * * @return bool|string */ public function has_temp_file( $id, $group ) { $temp_files = get_option( '_et_core_portability_temp_files', array() ); if ( isset( $temp_files[$group][$id] ) && file_exists( $temp_files[$group][$id] ) ) { return $temp_files[$group][$id]; } return false; } /** * Create a temp file and register it. * * @since 2.7.0 * @since 4.0 Made method public. Added $content parameter. * * @param string $id Unique id reference for the temporary file. * @param string $group Group name in which files are grouped. * @param string|bool $temp_file Path to the temporary file. False create a new temporary file. * * @return bool|string */ public function temp_file( $id, $group, $temp_file = false, $content = '' ) { $temp_files = get_option( '_et_core_portability_temp_files', array() ); if ( ! isset( $temp_files[$group] ) ) { $temp_files[$group] = array(); } if ( isset( $temp_files[$group][$id] ) && file_exists( $temp_files[$group][$id] ) ) { return $temp_files[$group][$id]; } $temp_file = $temp_file ? $temp_file : wp_tempnam(); $temp_files[$group][$id] = $temp_file; update_option( '_et_core_portability_temp_files', $temp_files, false ); if ( ! empty( $content ) ) { $this->get_filesystem()->put_contents( $temp_file, $content ); } return $temp_file; } /** * Get temp file contents or an empty string if it does not exist. * * @since 4.0 * * @param string $id Unique id used when the temporary file was created. * @param string $group Group name in which files are grouped. * * @return string */ public function get_temp_file_contents( $id, $group ) { $file = $this->has_temp_file( $id, $group ); if ( ! $file ) { return ''; } $content = $this->get_filesystem()->get_contents( $file ); return $content ? $content : ''; } /** * Delete all the temp files. * * @since 2.7.0 * * @param bool|string $group Group name in which files are grouped. Set to true to remove all groups and files. * @param array $defined_files Array or temoporary files to delete. No argument deletes all temp files. */ public function delete_temp_files( $group = false, $defined_files = false ) { $filesystem = $this->set_filesystem(); $temp_files = get_option( '_et_core_portability_temp_files', array() ); // Remove all temp files accross all groups if group is true. if ( $group === true ) { foreach( $temp_files as $group_id => $_group ) { $this->delete_temp_files( $group_id ); } } if ( ! isset( $temp_files[$group] ) ) { return; } $delete_files = ( is_array( $defined_files ) && ! empty( $defined_files ) ) ? $defined_files : $temp_files[$group]; foreach ( $delete_files as $id => $temp_file ) { if ( isset( $temp_files[$group][$id] ) && $filesystem->delete( $temp_files[$group][$id] ) ) { unset( $temp_files[$group][$id] ); } } if ( empty( $temp_files[$group] ) ) { unset( $temp_files[$group] ); } if ( empty( $temp_files ) ) { delete_option( '_et_core_portability_temp_files' ); } else { update_option( '_et_core_portability_temp_files', $temp_files, false ); } } /** * Set WP filesystem method to direct. * * @since 2.7.0 */ public function replace_filesystem_method() { return 'direct'; } /** * Get timestamp or create one if it isn't set. * * @since 2.7.0 */ public function get_timestamp() { et_core_nonce_verified_previously(); if ( isset( $_POST['timestamp'] ) && ! empty( $_POST['timestamp'] ) ) { return sanitize_text_field( $_POST['timestamp'] ); } return function_exists( 'hrtime' ) ? (string) hrtime( true ) : (string) microtime( true ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.hrtimeFound -- Intentional use of new PHP function } /** * Get Global Colors array from provided global_colors_info. * * @since 4.10.8 * * @param array $global_colors_info Array of global colors to process. * * @return array The list of the Global Colors for export. */ protected function _get_global_colors_data( $global_colors_info = array() ) { $global_color_ids = array_unique( array_keys( $global_colors_info ) ); if ( empty( $global_color_ids ) ) { return array(); } $all_global_colors = et_builder_get_all_global_colors(); $used_colors = array(); foreach ( $global_color_ids as $color_id ) { if ( isset( $all_global_colors[ $color_id ] ) ) { $color_data = array( $color_id, $all_global_colors[ $color_id ], ); $used_colors[] = $color_data; } } return $used_colors; } /** * Get List of global colors used in shortcode. * * @since 4.10.8 * * @param array $shortcode_object The multidimensional array representing a page structure. * @param array $used_global_colors List of global colors to merge with. * @param array $presets Object of presets. * * @return array - The list of the Global Colors. */ protected function _get_used_global_colors( $shortcode_object, $used_global_colors = array(), $presets = array() ) { foreach ( $shortcode_object as $module ) { if ( isset( $module['attrs']['global_colors_info'] ) ) { // Retrive global_colors_info from post meta, which saved as string[][]. $gc_info_prepared = str_replace( array( '[', ']' ), array( '[', ']' ), $module['attrs']['global_colors_info'] ); // Make sure we pass array to array_merge to avoid Fatal Error. $gc_info_array = json_decode( $gc_info_prepared, true ); $gc_info_array = is_array( $gc_info_array ) ? $gc_info_array : []; $used_global_colors = array_merge( $used_global_colors, $gc_info_array ); } if ( isset( $module['content'] ) && is_array( $module['content'] ) ) { $used_global_colors = array_merge( $used_global_colors, $this->_get_used_global_colors( $module['content'], $used_global_colors, $presets ) ); } } if ( ! empty( $presets ) ) { foreach ( $presets as $module_type => $module_presets ) { foreach ( $module_presets->presets as $preset_id => $preset ) { if ( isset( $preset->settings->global_colors_info ) ) { $used_global_colors = array_merge( $used_global_colors, json_decode( $preset->settings->global_colors_info, true ) ); } } } } return $used_global_colors; } /** * Returns Global Presets used for a given shortcode only * * @since 3.26 * * @param array $shortcode_object - The multidimensional array representing a page structure * @param array $used_global_presets * * @return array - The list of the Global Presets * */ protected function get_used_global_presets( $shortcode_object, $used_global_presets = array() ) { $global_presets_manager = ET_Builder_Global_Presets_Settings::instance(); foreach ( $shortcode_object as $module ) { $module_type = $global_presets_manager->maybe_convert_module_type( $module['type'], $module['attrs'] ); $preset_id = $global_presets_manager->get_module_preset_id( $module_type, $module['attrs'] ); $preset = $global_presets_manager->get_module_preset( $module_type, $preset_id ); if ( $preset_id !== 'default' && count( (array) $preset ) !== 0 && count( (array) $preset->settings ) !== 0 ) { if ( ! isset( $used_global_presets[ $module_type ] ) ) { $used_global_presets[ $module_type ] = (object) array( 'presets' => (object) array(), ); } if ( ! isset( $used_global_presets[ $module_type ]->presets->$preset_id ) ) { $used_global_presets[ $module_type ]->presets->$preset_id = (object) array( 'name' => $preset->name, 'version' => $preset->version, 'settings' => $preset->settings, ); } if ( ! isset( $used_global_presets[ $module_type ]->default ) ) { $used_global_presets[ $module_type ]->default = $global_presets_manager->get_module_default_preset_id( $module_type ); } } if ( isset( $module['content'] ) && is_array( $module['content'] ) ) { $used_global_presets = array_merge( $used_global_presets, $this->get_used_global_presets( $module['content'], $used_global_presets ) ); } } return $used_global_presets; } /** * Returns Global Colors used for a given theme builder shortcode. * * @since 4.18.0 * * @param array $shortcode_object - The multidimensional array representing a page structure. * * @return array The list of the Global Colors */ public function get_theme_builder_library_used_global_colors( $shortcode_object ) { return $this->_get_used_global_colors( $shortcode_object ); } /** * Returns Global Presets used for a given theme builder shortcode. * * @since 4.18.0 * * @param array $shortcode_object - The multidimensional array representing a page structure. * * @return array The list of the Global Presets */ public function get_theme_builder_library_used_global_presets( $shortcode_object ) { return $this->get_used_global_presets( $shortcode_object ); } /** * Returns images used for a given theme builder shortcode. * * @since 4.18.0 * * @param array $data - ID and Post content. * * @return array The list of the encoded images */ public function get_theme_builder_library_images( $data ) { $timestamp = $this->get_timestamp(); $images = $this->get_data_images( $data ); return $this->maybe_paginate_images( $images, 'encode_images', $timestamp ); } /** * Returns thumbnails used for a given theme builder shortcode. * * @since 4.18.0 * * @param array $data - ID and Post content. * * @return array The list of the thumbnails */ public function get_theme_builder_library_thumbnail_images( $data ) { return $this->_get_thumbnail_images( $data ); } /** * Enqueue assets. * * @since ?.? Script `et-core-portability` now loads in footer along with `et-core-admin`. * @since 2.7.0 */ public function assets() { $time = '<span>1</span>'; wp_enqueue_style( 'et-core-portability', ET_CORE_URL . 'admin/css/portability.css', array( 'et-core-admin', ), ET_CORE_VERSION ); wp_enqueue_script( 'et-core-portability', ET_CORE_URL . 'admin/js/portability.js', array( 'jquery', 'jquery-ui-tabs', 'jquery-form', 'et-core-admin', ), ET_CORE_VERSION, true ); wp_localize_script( 'et-core-portability', 'etCorePortability', array( 'nonces' => array( 'import' => wp_create_nonce( 'et_core_portability_import' ), 'export' => wp_create_nonce( 'et_core_portability_export' ), 'cancel' => wp_create_nonce( 'et_core_portability_cancel' ), 'presets' => wp_create_nonce( 'et_core_portability_import_default_presets' ), ), 'postMaxSize' => $this->to_megabytes( @ini_get( 'post_max_size' ) ), 'uploadMaxSize' => $this->to_megabytes( @ini_get( 'upload_max_filesize' ) ), 'text' => array( // phpcs:disable WordPress.WP.I18n.NonSingularStringLiteralDomain -- Following the standard. 'browserSupport' => esc_html__( 'The browser version you are currently using is outdated. Please update to the newest version.', ET_CORE_TEXTDOMAIN ), 'memoryExhausted' => esc_html__( 'You reached your server memory limit. Please try increasing your PHP memory limit.', ET_CORE_TEXTDOMAIN ), 'maxSizeExceeded' => esc_html__( 'This file cannot be imported. It may be caused by file_uploads being disabled in your php.ini. It may also be caused by post_max_size or/and upload_max_filesize being smaller than file selected. Please increase it or transfer more substantial data at the time.', ET_CORE_TEXTDOMAIN ), 'invalideFile' => esc_html__( 'Invalid File format. You should be uploading a JSON file.', ET_CORE_TEXTDOMAIN ), 'importContextFail' => esc_html__( 'This file should not be imported in this context.', ET_CORE_TEXTDOMAIN ), 'noItemsSelected' => esc_html__( 'Please select at least one item to export or disable the "Only export selected items" option', ET_CORE_TEXTDOMAIN ), 'noItemsToExport' => esc_html__( 'There are no items to export.', ET_CORE_TEXTDOMAIN ), 'importing' => sprintf( esc_html__( 'Import estimated time remaining: %smin', ET_CORE_TEXTDOMAIN ), $time ), 'exporting' => sprintf( esc_html__( 'Export estimated time remaining: %smin', ET_CORE_TEXTDOMAIN ), $time ), 'backuping' => sprintf( esc_html__( 'Backup estimated time remaining: %smin', ET_CORE_TEXTDOMAIN ), $time ), // phpcs:enable ), ) ); } /** * Modal HTML. * * @since 2.7.0 */ public function modal() { $export_url = add_query_arg( array( 'et_core_portability' => true, 'context' => $this->instance->context, 'name' => $this->instance->name, 'nonce' => wp_create_nonce( 'et_core_portability_export' ), ), admin_url() ); $is_etdev_plugin_activated = is_plugin_active( 'etdev/etdev.php' ); // phpcs:disable WordPress.WP.I18n.NonSingularStringLiteralDomain -- Existing codebase. ?> <div class="et-core-modal-overlay et-core-form" data-et-core-portability="<?php echo esc_attr( $this->instance->context ); ?>"> <div class="et-core-modal"> <div class="et-core-modal-header"> <h3 class="et-core-modal-title"><?php echo esc_html( $this->instance->title ); ?></h3><a href="#" class="et-core-modal-close" data-et-core-modal="close"></a> </div> <div data-et-core-tabs class="et-core-modal-tabs-enabled"> <ul class="et-core-tabs"> <li><a href="#et-core-portability-export"><?php esc_html_e( 'Export', ET_CORE_TEXTDOMAIN ); ?></a></li> <li><a href="#et-core-portability-import"><?php esc_html_e( 'Import', ET_CORE_TEXTDOMAIN ); ?></a></li> </ul> <div id="et-core-portability-export"> <div class="et-core-modal-content"> <?php printf( esc_html__( 'Exporting your %s will create a JSON file that can be imported into a different website.', ET_CORE_TEXTDOMAIN ), esc_html( $this->instance->name ) ); ?> <h3><?php esc_html_e( 'Export File Name', ET_CORE_TEXTDOMAIN ); ?></h3> <form class="et-core-portability-export-form"> <input type="text" name="" value="<?php echo esc_attr( $this->instance->name ); ?>"> <?php if ( 'post_type' === $this->instance->type ) : ?> <div class="et-core-clearfix"></div> <label><input type="checkbox" name="et-core-portability-posts" <?php echo $is_etdev_plugin_activated ? 'checked' : ''; ?> /><?php esc_html_e( 'Only export selected items', ET_CORE_TEXTDOMAIN ); ?></label> <?php endif; ?> <?php if ( $is_etdev_plugin_activated ) : ?> <div class="et-core-clearfix"></div> <label><input type="checkbox" name="et-core-portability-apply-presets" checked /><?php esc_html_e( 'Export Presets As Static Styles', ET_CORE_TEXTDOMAIN ); ?></label> <?php endif; ?> </form> </div> <div class="et-core-action-buttons-container"> <a class="et-core-modal-action et-core-button-primary" href="#" data-et-core-portability-export="<?php echo esc_url( $export_url ); ?>"><?php esc_html_e( 'Download Export', ET_CORE_TEXTDOMAIN ); ?></a> <?php if ( 'et_builder_layouts' === $this->instance->context ) { ?> <a class="et-core-modal-action et-core-button-secondary" href="#" data-et-core-portability-export-to-cloud="1"><?php esc_html_e( 'Export To Divi Cloud', 'et-core' ); ?></a> <?php } ?> </div> <div class="et-core-action-buttons-container__during_action"> <a class="et-core-modal-action et-core-button-danger" href="#" data-et-core-portability-cancel><?php esc_html_e( 'Cancel Export', ET_CORE_TEXTDOMAIN ); ?></a> </div> </div> <div id="et-core-portability-import"> <div class="et-core-modal-content"> <?php if ( 'post' === $this->instance->type ) : ?> <?php printf( esc_html__( 'Importing a previously-exported %s file will overwrite all content currently on this page.', ET_CORE_TEXTDOMAIN ), esc_html( $this->instance->name ) ); ?> <?php elseif ( 'post_type' === $this->instance->type ) : ?> <?php printf( esc_html__( 'Select a previously-exported Divi Builder Layouts file to begin importing items. Large collections of image-heavy exports may take several minutes to upload.', ET_CORE_TEXTDOMAIN ), esc_html( $this->instance->name ) ); ?> <?php else : ?> <?php printf( esc_html__( 'Importing a previously-exported %s file will overwrite all current data. Please proceed with caution!', ET_CORE_TEXTDOMAIN ), esc_html( $this->instance->name ) ); ?> <?php endif; ?> <h3><?php esc_html_e( 'Select File To Import', ET_CORE_TEXTDOMAIN ); ?></h3> <form class="et-core-portability-import-form"> <span class="et-core-portability-import-placeholder"><?php esc_html_e( 'No File Selected', ET_CORE_TEXTDOMAIN ); ?></span> <button class="et-core-button"><?php esc_html_e( 'Choose File', ET_CORE_TEXTDOMAIN ); ?></button> <input type="file"> <div class="et-core-clearfix"></div> <?php if ( 'post_type' !== $this->instance->type ) : ?> <label><input type="checkbox" name="et-core-portability-import-backup" /><?php esc_html_e( 'Download Backup Before Importing', ET_CORE_TEXTDOMAIN ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralDomain -- intentional use of ET_CORE_TEXTDOMAIN ?></label> <?php endif; ?> <?php if ( 'post_type' === $this->instance->type ) : ?> <label><input type="checkbox" name="et-core-portability-import-include-global-presets" /><?php esc_html_e( 'Import Presets', ET_CORE_TEXTDOMAIN ); ?></label> <?php endif; ?> </form> </div> <a class="et-core-modal-action et-core-portability-import" href="#"><?php printf( esc_html__( 'Import %s', ET_CORE_TEXTDOMAIN ), esc_html( $this->instance->name ) ); ?></a> <a class="et-core-modal-action et-core-button-danger" href="#" data-et-core-portability-cancel><?php esc_html_e( 'Cancel Import', ET_CORE_TEXTDOMAIN ); ?></a> </div> </div> </div> </div> <?php // phpcs:enable } } if ( ! function_exists( 'et_core_portability_register' ) ) : /** * Register portability. * * This function should be called in an 'admin_init' action callback. * * @since 2.7.0 * * @param string $context A unique ID used to register the portability arguments. * * @param array $args { * Array of arguments used to register the portability. * * @type string $name The name used in the various text string. * @type bool $view Whether the assets and content should load or not. * Example: `isset( $_GET['page'] ) && $_GET['page'] == 'example'`. * @type string $db The option_name from the wp_option table used to export and import data. * @type array $include Optional. Array of all the options scritcly included. Options ids must be set * as the array keys. * @type array $exclude Optional. Array of excluded options. Options ids must be set as the array keys. * } */ function et_core_portability_register( $context, $args ) { $defaults = array( 'context' => $context, 'title' => esc_html__( 'Portability', ET_CORE_TEXTDOMAIN ), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralDomain -- intentional use of ET_CORE_TEXTDOMAIN 'name' => false, 'view' => false, 'type' => false, 'target' => false, 'include' => array(), 'exclude' => array(), ); $data = apply_filters( "et_core_portability_args_{$context}", (object) array_merge( $defaults, (array) $args ) ); et_core_cache_set( $context, $data, 'et_core_portability' ); // Stop here if not allowed. if ( function_exists( 'et_pb_is_allowed' ) && ! et_pb_is_allowed( array( 'portability', "{$data->context}_portability" ) ) ) { // Set view to false if not allowed. $data->view = false; et_core_cache_set( $context, $data, 'et_core_portability' ); return; } if ( $data->view ) { et_core_portability_load( $context ); } } endif; if ( ! function_exists( 'et_core_portability_load' ) ) : /** * Load Portability class. * * @since 2.7.0 * * @param string $context A unique ID used to register the portability arguments. * @return ET_Core_Portability */ function et_core_portability_load( $context ) { return new ET_Core_Portability( $context ); } endif; if ( ! function_exists( 'et_core_portability_link' ) ) : /** * HTML link to trigger the portability modal. * * @since 2.7.0 * * @param string $context The context used to register the portability. * @param string|array $attributes Optional. Query string or array of attributes. Default empty. * * @return string */ function et_core_portability_link( $context, $attributes = array() ) { $instance = et_core_cache_get( $context, 'et_core_portability' ); if ( ! $capability = et_core_portability_cap( $context ) ) { return ''; } if ( ! current_user_can( $capability ) || ! ( isset( $instance->view ) && $instance->view ) ) { return ''; } $defaults = array( 'title' => esc_attr__( 'Import & Export', ET_CORE_TEXTDOMAIN ), ); $attributes = array_merge( $defaults, $attributes ); // Forced attributes. $attributes['href'] = '#'; $context = esc_attr( $context ); $attributes['data-et-core-modal'] = "[data-et-core-portability='{$context}']"; $string = ''; foreach ( $attributes as $attribute => $value ) { if ( null !== $value ){ $string .= esc_attr( $attribute ) . '="' . esc_attr( $value ) . '" '; } } return sprintf( '<a %1$s><span>%2$s</span></a>', trim( $string ), esc_html( $attributes['title'] ) ); } endif; if ( ! function_exists( 'et_core_portability_ajax_import' ) ) : /** * Ajax portability Import. * * @since 2.7.0 */ function et_core_portability_ajax_import() { if ( ! isset( $_POST['context'] ) ) { et_core_die(); } $context = sanitize_text_field( $_POST['context'] ); $post_id = isset( $_POST['post'] ) ? (int) $_POST['post'] : 0; $replace = isset( $_POST['replace'] ) ? '1' === $_POST['replace'] : false; if ( ! $capability = et_core_portability_cap( $context ) ) { et_core_die(); } if ( ! et_core_security_check_passed( $capability, 'et_core_portability_import', 'nonce' ) ) { et_core_die(); } $portability = et_core_portability_load( $context ); if ( ! $result = $portability->import() ) { wp_send_json_error(); } else if ( is_array( $result ) && isset( $result['message'] ) ) { wp_send_json_error( $result ); } else if ( $result ) { if ( $replace && $post_id > 0 && current_user_can( 'edit_post', $post_id ) ) { wp_update_post( array( 'ID' => $post_id, 'post_content' => $result['postContent'], ) ); } wp_send_json_success( $result ); } wp_send_json_error(); } add_action( 'wp_ajax_et_core_portability_import', 'et_core_portability_ajax_import' ); endif; if ( ! function_exists( 'et_core_portability_ajax_export' ) ) : /** * Ajax portability Export. * * @since 2.7.0 */ function et_core_portability_ajax_export() { if ( ! isset( $_POST['context'] ) ) { wp_send_json_error(); return; } $context = sanitize_text_field( $_POST['context'] ); // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure, WordPress.CodeAnalysis.AssignmentInCondition.Found -- Existing codebase. if ( ! $capability = et_core_portability_cap( $context ) ) { wp_send_json_error(); return; } if ( ! et_core_security_check_passed( $capability, 'et_core_portability_export', 'nonce' ) ) { wp_send_json_error(); return; } $return = isset( $_POST['return'] ) && sanitize_text_field( $_POST['return'] ); if ( $return ) { $data = et_core_portability_load( $context )->export( $return ); wp_send_json_success( $data ); } else { et_core_portability_load( $context )->export(); } wp_send_json_error(); } add_action( 'wp_ajax_et_core_portability_export', 'et_core_portability_ajax_export' ); endif; if ( ! function_exists( 'et_core_portability_ajax_cancel' ) ) : /** * Cancel portability action. * * @since 2.7.0 */ function et_core_portability_ajax_cancel() { if ( ! isset( $_POST['context'] ) ) { et_core_die(); } $context = sanitize_text_field( $_POST['context'] ); if ( ! $capability = et_core_portability_cap( $context ) ) { et_core_die(); } if ( ! et_core_security_check_passed( $capability, 'et_core_portability_cancel' ) ) { et_core_die(); } et_core_portability_load( $context )->delete_temp_files( true ); wp_send_json_error(); } add_action( 'wp_ajax_et_core_portability_cancel', 'et_core_portability_ajax_cancel' ); endif; if ( ! function_exists( 'et_core_upload_and_get_urls_from_presets_images' ) ) : /** * Upload images and return the new URLs for presets. * * @since 4.26.1 * @param array $data Array of images to upload. * * @return array */ function et_core_upload_and_get_urls_from_presets_images( $data ) { if ( ! current_user_can( 'manage_options' ) ) { return; } // Require the file that includes wp_generate_attachment_metadata(). require_once( ABSPATH . 'wp-admin/includes/image.php' ); $mime_types = get_allowed_mime_types(); $wp_upload_dir = wp_upload_dir(); $uploaded_urls = []; foreach ( $data as $imageInfo ) { $image_data = base64_decode( $imageInfo['encoded'] ); $temp_file = tempnam( $wp_upload_dir['path'], 'preset_' ); $filetype = wp_check_filetype( basename( $temp_file ), null ); if ( ! in_array( $filetype['type'], $mime_types, true ) ) { continue; } file_put_contents( $temp_file, $image_data ); $attachment = [ 'guid' => $wp_upload_dir['url'] . '/' . basename( $temp_file ), 'post_mime_type' => $filetype['type'], 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $temp_file ) ), 'post_content' => '', 'post_status' => 'inherit' ]; $attach_id = wp_insert_attachment( $attachment, $temp_file ); $attach_data = wp_generate_attachment_metadata( $attach_id, $temp_file ); wp_update_attachment_metadata( $attach_id, $attach_data ); $uploaded_urls[] = [ 'old_url' => $imageInfo['url'], 'new_url' => wp_get_attachment_url( $attach_id ), ]; unlink( $temp_file ); } return $uploaded_urls; } endif; if ( ! function_exists( 'et_core_replace_image_urls_in_presets' ) ) : /** * Replace image URLs in presets JSON string. */ function et_core_replace_image_urls_in_presets( $presets, $uploaded_urls ) { foreach ( $uploaded_urls as $url ) { $presets = str_replace( $url['old_url'], $url['new_url'], $presets ); } return $presets; } endif; if ( ! function_exists( 'et_core_portability_import_default_presets' ) ) : /** * Set the default preset for a modules. * * @since 4.26.0 */ function et_core_portability_import_default_presets() { if ( ! et_core_security_check_passed( 'manage_options', 'et_core_portability_import_default_presets', 'nonce' ) ) { et_core_die(); } $presets = isset( $_POST['presets'] ) ? sanitize_text_field( $_POST['presets'] ) : ''; $preset_prefix = isset( $_POST['presetPrefix'] ) ? sanitize_text_field( $_POST['presetPrefix'] ) : ''; $global_colors = isset( $_POST['globalColors'] ) ? sanitize_text_field( $_POST['globalColors'] ) : ''; $images = isset( $_POST['images'] ) ? sanitize_text_field( $_POST['images'] ) : ''; $uploaded_urls = []; $portability = et_core_portability_load( 'et_builder' ); if ( $global_colors ) { $global_colors = json_decode( stripslashes( $global_colors ), true ); $portability->import_global_colors( $global_colors ); } if ( $images ) { $images = json_decode( stripslashes( $images ), true ); $uploaded_urls = et_core_upload_and_get_urls_from_presets_images( $images ); } if ( $presets ) { if ( ! empty( $uploaded_urls ) ) { $presets = et_core_replace_image_urls_in_presets( $presets, $uploaded_urls ); } $presets = json_decode( stripslashes( $presets ), true ); $portability->import_global_presets( $presets, false, true, $preset_prefix ); } ET_Core_PageResource::remove_static_resources( 'all', 'all' ); wp_send_json_success(); } add_action( 'wp_ajax_et_core_portability_import_default_presets', 'et_core_portability_import_default_presets' ); endif; if ( ! function_exists( 'et_core_portability_export' ) ) : /** * Portability export. * * @since 2.7.0 */ function et_core_portability_export() { if ( ! isset( $_GET['et_core_portability'], $_GET['timestamp'] ) ) { return; } if ( ! et_core_security_check_passed( 'edit_posts' ) ) { wp_die( esc_html__( 'The export process failed. Please refresh the page and try again.', ET_CORE_TEXTDOMAIN ) ); } et_core_portability_load( sanitize_text_field( $_GET['timestamp'] ) )->download_export(); } add_action( 'admin_init', 'et_core_portability_export', 20 ); endif; if ( ! function_exists( 'et_core_portability_cap' ) ): /** * Returns the required WordPress Capability for a Portability context. * * @since 3.0.91 * * @param string $context The Portability context * * @return string */ function et_core_portability_cap( $context ) { $capability = ''; $options_contexts = array( 'et_pb_roles', 'epanel', 'epanel_temp', 'et_divi_mods', 'et_extra_mods', ); $post_contexts = array( 'et_builder', 'et_theme_builder', 'et_code_snippets', 'et_builder_layouts', ); if ( in_array( $context, $options_contexts, true ) ) { $capability = 'edit_theme_options'; } else if ( in_array( $context, $post_contexts, true ) ) { $capability = 'edit_posts'; } return $capability; } endif; Cache.php 0000644 00000021343 15222651263 0006267 0 ustar 00 <?php /** * Core Cache implements an object cache. * * The Object Cache stores all of the cache data to memory only for the duration of the request. * * @package Core\Cache */ /** * Core class that implements an object cache. * * The Object Cache is used to save on trips to the database. The * Object Cache stores all of the cache data to memory and makes the cache * contents available by using a key, which is used to name and later retrieve * the cache contents. * * @private * * @package ET\Core\Cache */ final class ET_Core_Cache { /** * Cached data. * * @since 1.0.0 * * @type array */ private static $cache = array(); /** * Adds data to the cache if it doesn't already exist. * * @since 1.0.0 * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @return bool False if cache key and group already exist, true on success */ public static function add( $key, $data, $group = 'default' ) { if ( empty( $group ) ) { $group = 'default'; } if ( self::_exists( $key, $group ) ) { return false; } return self::set( $key, $data, $group ); } /** * Sets the data contents into the cache. * * The cache contents is grouped by the $group parameter followed by the * $key. This allows for duplicate ids in unique groups. * * @since 1.0.0 * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Not Used. * @return true Always returns true. */ public static function set( $key, $data, $group = 'default' ) { if ( empty( $group ) ) { $group = 'default'; } if ( is_object( $data ) ) { $data = clone $data; } self::$cache[ $group ][ $key ] = $data; return true; } /** * Retrieves the cache contents, if it exists. * * The contents will be first attempted to be retrieved by searching by the * key in the cache group. If the cache is hit (success) then the contents * are returned. * * @since 1.0.0 * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return false|mixed False on failure to retrieve contents or the cache contents on success. */ public static function get( $key, $group = 'default' ) { if ( empty( $group ) ) { $group = 'default'; } if ( self::_exists( $key, $group ) ) { if ( is_object( self::$cache[ $group ][ $key ] ) ) { return clone self::$cache[ $group ][ $key ]; } else { return self::$cache[ $group ][ $key ]; } } return false; } /** * Retrieves the cache contents for entire group, if it exists. * * If the cache is hit (success) then the contents of the group * are returned. * * @since 1.0.0 * * @param string $group Where the cache contents are grouped. * @return false|mixed False on failure to retrieve contents or the cache contents on success. */ public static function get_group( $group ) { if ( isset( self::$cache[ $group ] ) ) { return self::$cache[ $group ]; } return false; } /** * Check the cache contents, if given key and (optional) group exists. * * @since 1.0.0 * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return bool False on failure to retrieve contents or True on success. */ public static function has( $key, $group = 'default' ) { if ( empty( $group ) ) { $group = 'default'; } return self::_exists( $key, $group ); } /** * Removes the contents of the cache key in the group. * * If the cache key does not exist in the group, then nothing will happen. * * @since 1.0.0 * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return bool False if the contents weren't deleted and true on success. */ public static function delete( $key, $group = 'default' ) { if ( empty( $group ) ) { $group = 'default'; } if ( ! self::_exists( $key, $group ) ) { return false; } unset( self::$cache[ $group ][ $key ] ); return true; } /** * Clears the object cache of all data. * * @since 1.0.0 * * @return true Always returns true. */ public static function flush() { self::$cache = array(); return true; } /** * Serves as a utility function to determine whether a key exists in the cache. * * @since 1.0.0 * @private * * @param int|string $key Cache key to check for existence. * @param string $group Cache group for the key existence check. * @return bool Whether the key exists in the cache for the given group. */ private static function _exists( $key, $group ) { return isset( self::$cache[ $group ] ) && isset( self::$cache[ $group ][ $key ] ); } } if ( ! function_exists( 'et_core_cache_add' ) ) : /** * Adds data to the cache if it doesn't already exist. * * @since 1.0.0 * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @return bool False if cache key and group already exist, true on success */ function et_core_cache_add( $key, $data, $group = '' ) { return ET_Core_Cache::add( $key, $data, $group ); } endif; if ( ! function_exists( 'et_core_cache_set' ) ) : /** * Sets the data contents into the cache. * * The cache contents is grouped by the $group parameter followed by the * $key. This allows for duplicate ids in unique groups. * * @since 1.0.0 * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Not Used. * @return true Always returns true. */ function et_core_cache_set( $key, $data, $group = '' ) { return ET_Core_Cache::set( $key, $data, $group ); } endif; if ( ! function_exists( 'et_core_cache_get' ) ) : /** * Retrieves the cache contents, if it exists. * * The contents will be first attempted to be retrieved by searching by the * key in the cache group. If the cache is hit (success) then the contents * are returned. * * @since 1.0.0 * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return false|mixed False on failure to retrieve contents or the cache contents on success. */ function et_core_cache_get( $key, $group = '' ) { return ET_Core_Cache::get( $key, $group ); } endif; if ( ! function_exists( 'et_core_cache_get_group' ) ) : /** * Retrieves the cache contents for entire group, if it exists. * * If the cache is hit (success) then the contents of the group * are returned. * * @since 1.0.0 * * @param string $group Where the cache contents are grouped. * @return false|mixed False on failure to retrieve contents or the cache contents on success. */ function et_core_cache_get_group( $group ) { return ET_Core_Cache::get_group( $group ); } endif; if ( ! function_exists( 'et_core_cache_has' ) ) : /** * Check the cache contents, if given key and (optional) group exists. * * @since 1.0.0 * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return bool False on failure to retrieve contents or True on success. */ function et_core_cache_has( $key, $group = '' ) { return ET_Core_Cache::has( $key, $group ); } endif; if ( ! function_exists( 'et_core_cache_delete' ) ) : /** * Removes the contents of the cache key in the group. * * If the cache key does not exist in the group, then nothing will happen. * * @since 1.0.0 * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return bool False if the contents weren't deleted and true on success. */ function et_core_cache_delete( $key, $group = '' ) { return ET_Core_Cache::delete( $key, $group ); } endif; if ( ! function_exists( 'et_core_cache_flush' ) ) : /** * Clears the object cache of all data. * * @since 1.0.0 * @return true Always returns true. */ function et_core_cache_flush() { return ET_Core_Cache::flush(); } endif; init.php 0000644 00000030273 15222651263 0006231 0 ustar 00 <?php // phpcs:disable Generic.WhiteSpace.ScopeIndent -- our preference is to not indent the whole inner function in this scenario. if ( ! function_exists( 'et_core_init' ) ) : /** * {@see 'plugins_loaded' (9999999) Must run after cache plugins have been loaded.} */ function et_core_init() { ET_Core_API_Spam_Providers::instance(); ET_Core_Cache_Directory::instance(); ET_Core_PageResource::startup(); ET_Core_CompatibilityWarning::instance(); if ( defined( 'ET_CORE_UPDATED' ) ) { global $wp_rewrite; add_action( 'shutdown', array( $wp_rewrite, 'flush_rules' ) ); update_option( 'et_core_page_resource_remove_all', true ); } $cache_dir = ET_Core_PageResource::get_cache_directory(); if ( file_exists( $cache_dir . '/DONOTCACHEPAGE' ) ) { ! defined( 'DONOTCACHEPAGE' ) ? define( 'DONOTCACHEPAGE', true ) : ''; @unlink( $cache_dir . '/DONOTCACHEPAGE' ); } if ( get_option( 'et_core_page_resource_remove_all' ) ) { ET_Core_PageResource::remove_static_resources( 'all', 'all', true ); } } endif; if ( ! function_exists( 'et_core_site_has_builder' ) ) : /** * Check is `et_core_site_has_builder` allowed. * We can clear cache managed by 3rd party plugins only * if Divi, Extra, or the Divi Builder plugin * is active when the core was called. * * @return boolean */ function et_core_site_has_builder() { global $shortname; $core_path = get_transient( 'et_core_path' ); $is_divi_builder_plugin_active = false; if ( ! empty( $core_path ) && false !== strpos( $core_path, '/divi-builder/' ) && function_exists('is_plugin_active') ) { $is_divi_builder_plugin_active = is_plugin_active( 'divi-builder/divi-builder.php' ); } if( $is_divi_builder_plugin_active || in_array( $shortname, array( 'divi', 'extra' ) ) ) { return true; } return false; } endif; if ( ! function_exists( 'et_core_clear_wp_cache' ) ): function et_core_clear_wp_cache( $post_id = '' ) { if ( ( ! wp_doing_cron() && ! et_core_security_check_passed( 'edit_posts' ) ) || ! et_core_site_has_builder() ) { return; } try { // Cache Plugins // Comet Cache if ( is_callable( 'comet_cache::clear' ) ) { comet_cache::clear(); } // WP Rocket if ( function_exists( 'rocket_clean_post' ) ) { if ( '' !== $post_id ) { rocket_clean_post( $post_id ); } else if ( function_exists( 'rocket_clean_domain' ) ) { rocket_clean_domain(); } } // W3 Total Cache if ( has_action( 'w3tc_flush_post' ) ) { '' !== $post_id ? do_action( 'w3tc_flush_post', $post_id ) : do_action( 'w3tc_flush_posts' ); } // WP Super Cache if ( function_exists( 'wp_cache_debug' ) && defined( 'WPCACHEHOME' ) ) { include_once WPCACHEHOME . 'wp-cache-phase1.php'; include_once WPCACHEHOME . 'wp-cache-phase2.php'; if ( '' !== $post_id && function_exists( 'clear_post_supercache' ) ) { clear_post_supercache( $post_id ); } else if ( '' === $post_id && function_exists( 'wp_cache_clear_cache_on_menu' ) ) { wp_cache_clear_cache_on_menu(); } } // WP Fastest Cache if ( isset( $GLOBALS['wp_fastest_cache'] ) ) { if ( '' !== $post_id && method_exists( $GLOBALS['wp_fastest_cache'], 'singleDeleteCache' ) ) { $GLOBALS['wp_fastest_cache']->singleDeleteCache( $post_id ); } else if ( '' === $post_id && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) { $GLOBALS['wp_fastest_cache']->deleteCache(); } } // Hummingbird if ( has_action( 'wphb_clear_page_cache' ) ) { '' !== $post_id ? do_action( 'wphb_clear_page_cache', $post_id ) : do_action( 'wphb_clear_page_cache' ); } // WordPress Cache Enabler if ( has_action( 'ce_clear_cache' ) ) { '' !== $post_id ? do_action( 'ce_clear_post_cache', $post_id ) : do_action( 'ce_clear_cache' ); } // LiteSpeed Cache v3.0+. if ( '' !== $post_id && has_action( 'litespeed_purge_post' ) ) { do_action( 'litespeed_purge_post', $post_id ); } elseif ( '' === $post_id && has_action( 'litespeed_purge_all' ) ) { do_action( 'litespeed_purge_all' ); } // LiteSpeed Cache v1.1.3 until v3.0. if ( '' !== $post_id && function_exists( 'litespeed_purge_single_post' ) ) { litespeed_purge_single_post( $post_id ); } elseif ( '' === $post_id && is_callable( 'LiteSpeed_Cache_API::purge_all' ) ) { LiteSpeed_Cache_API::purge_all(); } elseif ( is_callable( 'LiteSpeed_Cache::get_instance' ) ) { // LiteSpeed Cache v1.1.3 below. LiteSpeed_Cache still exist on v2.9.9.2, but no // longer exist on v3.0. Keep it here as backward compatibility for lower version. $litespeed = LiteSpeed_Cache::get_instance(); if ( '' !== $post_id && method_exists( $litespeed, 'purge_post' ) ) { $litespeed->purge_post( $post_id ); } else if ( '' === $post_id && method_exists( $litespeed, 'purge_all' ) ) { $litespeed->purge_all(); } } // Hyper Cache if ( class_exists( 'HyperCache' ) && isset( HyperCache::$instance ) ) { if ( '' !== $post_id && method_exists( HyperCache::$instance, 'clean_post' ) ) { HyperCache::$instance->clean_post( $post_id ); } else if ( '' === $post_id && method_exists( HyperCache::$instance, 'clean' ) ) { HyperCache::$instance->clean_post( $post_id ); } } // Hosting Provider Caching // Pantheon Advanced Page Cache $pantheon_clear = 'pantheon_wp_clear_edge_keys'; $pantheon_clear_all = 'pantheon_wp_clear_edge_all'; if ( function_exists( $pantheon_clear ) || function_exists( $pantheon_clear_all ) ) { if ( '' !== $post_id && function_exists( $pantheon_clear ) ) { pantheon_wp_clear_edge_keys( array( "post-{$post_id}" ) ); } else if ( '' === $post_id && function_exists( $pantheon_clear_all ) ) { pantheon_wp_clear_edge_all(); } } // Siteground if ( isset( $GLOBALS['sg_cachepress_supercacher'] ) ) { global $sg_cachepress_supercacher; if ( is_object( $sg_cachepress_supercacher ) && method_exists( $sg_cachepress_supercacher, 'purge_cache' ) ) { $sg_cachepress_supercacher->purge_cache( true ); } } else if ( function_exists( 'sg_cachepress_purge_cache' ) ) { sg_cachepress_purge_cache(); } // WP Engine if ( class_exists( 'WpeCommon' ) ) { is_callable( 'WpeCommon::purge_memcached' ) ? WpeCommon::purge_memcached() : ''; is_callable( 'WpeCommon::clear_maxcdn_cache' ) ? WpeCommon::clear_maxcdn_cache() : ''; is_callable( 'WpeCommon::purge_varnish_cache' ) ? WpeCommon::purge_varnish_cache() : ''; if ( is_callable( 'WpeCommon::instance' ) && $instance = WpeCommon::instance() ) { method_exists( $instance, 'purge_object_cache' ) ? $instance->purge_object_cache() : ''; } } // Bluehost if ( class_exists( 'Endurance_Page_Cache' ) ) { wp_doing_ajax() ? ET_Core_LIB_BluehostCache::get_instance()->clear( $post_id ) : do_action( 'epc_purge' ); } // Pressable. if ( isset( $GLOBALS['batcache'] ) && is_object( $GLOBALS['batcache'] ) ) { wp_cache_flush(); } // Cloudways - Breeze. if ( class_exists( 'Breeze_Admin' ) ) { $breeze_admin = new Breeze_Admin(); $breeze_admin->breeze_clear_all_cache(); } // Kinsta. if ( class_exists( '\Kinsta\Cache' ) && isset( $GLOBALS['kinsta_cache'] ) && is_object( $GLOBALS['kinsta_cache'] ) ) { global $kinsta_cache; if ( isset( $kinsta_cache->kinsta_cache_purge ) && method_exists( $kinsta_cache->kinsta_cache_purge, 'purge_complete_caches' ) ) { $kinsta_cache->kinsta_cache_purge->purge_complete_caches(); } } // GoDaddy. if ( class_exists( '\WPaaS\Cache' ) ) { global $wpaas_cache_class; // Since GD System Plugin 4.51.1 the cache class instance can be accessed // with $wpaas_cache_class global. In addition to this, the 'has_ban' method // is no longer static. To cover both static and non-static versions we // can test if $wpaas_cache_class exists and use the correct type accordingly. $has_ban = $wpaas_cache_class ? $wpaas_cache_class->has_ban() : \WPaaS\Cache::has_ban(); if ( ! $has_ban ) { $gd_cache_class = $wpaas_cache_class ? $wpaas_cache_class : '\WPaaS\Cache'; remove_action( 'shutdown', array( $gd_cache_class, 'purge' ), PHP_INT_MAX ); add_action( 'shutdown', array( $gd_cache_class, 'ban' ), PHP_INT_MAX ); } } // Complimentary Performance Plugins. // Autoptimize. if ( is_callable( 'autoptimizeCache::clearall' ) ) { autoptimizeCache::clearall(); } // WP Optimize. if ( class_exists( 'WP_Optimize' ) && defined( 'WPO_PLUGIN_MAIN_PATH' ) ) { if ( '' !== $post_id && is_callable( 'WPO_Page_Cache::delete_single_post_cache' ) ) { WPO_Page_Cache::delete_single_post_cache( $post_id ); } elseif ( is_callable( array( 'WP_Optimize', 'get_page_cache' ) ) && is_callable( array( WP_Optimize()->get_page_cache(), 'purge' ) ) ) { WP_Optimize()->get_page_cache()->purge(); } } } catch( Exception $err ) { ET_Core_Logger::error( 'An exception occurred while attempting to clear site cache.' ); } } endif; if ( ! function_exists( 'et_core_get_nonces' ) ): /** * Returns the nonces for this component group. * * @return string[] */ function et_core_get_nonces() { static $nonces = null; return $nonces ? $nonces : $nonces = array( 'clear_page_resources_nonce' => wp_create_nonce( 'clear_page_resources' ), 'et_core_portability_export' => wp_create_nonce( 'et_core_portability_export' ), ); } endif; if ( ! function_exists( 'et_core_page_resource_auto_clear' ) ): function et_core_page_resource_auto_clear() { ET_Core_PageResource::remove_static_resources( 'all', 'all' ); } add_action( 'switch_theme', 'et_core_page_resource_auto_clear' ); add_action( 'activated_plugin', 'et_core_page_resource_auto_clear', 10, 0 ); add_action( 'deactivated_plugin', 'et_core_page_resource_auto_clear', 10, 0 ); endif; if ( ! function_exists( 'et_core_page_resource_clear' ) ): /** * Ajax handler for clearing cached page resources. */ function et_core_page_resource_clear() { et_core_security_check( 'manage_options', 'clear_page_resources' ); if ( empty( $_POST['et_post_id'] ) ) { et_core_die(); } $post_id = sanitize_key( $_POST['et_post_id'] ); $owner = sanitize_key( $_POST['et_owner'] ); ET_Core_PageResource::remove_static_resources( $post_id, $owner ); } add_action( 'wp_ajax_et_core_page_resource_clear', 'et_core_page_resource_clear' ); endif; if ( ! function_exists( 'et_core_page_resource_get' ) ): /** * Get a page resource instance. * * @param string $owner The owner of the instance (core|divi|builder|bloom|monarch|custom). * @param string $slug A string that uniquely identifies the resource. * @param string|int $post_id The post id that the resource is associated with or `global`. * If `null`, the return value of {@link get_the_ID()} will be used. * @param string $type The resource type (style|script). Default: `style`. * @param string $location Where the resource should be output (head|footer). Default: `head-late`. * * @return ET_Core_PageResource */ function et_core_page_resource_get( $owner, $slug, $post_id = null, $priority = 10, $location = 'head-late', $type = 'style' ) { $post_id = $post_id ? $post_id : et_core_page_resource_get_the_ID(); $_slug = "et-{$owner}-{$slug}-{$post_id}-cached-inline-{$type}s"; $all_resources = ET_Core_PageResource::get_resources(); return isset( $all_resources[ $_slug ] ) ? $all_resources[ $_slug ] : new ET_Core_PageResource( $owner, $slug, $post_id, $priority, $location, $type ); } endif; if ( ! function_exists( 'et_core_page_resource_get_the_ID' ) ): function et_core_page_resource_get_the_ID() { static $post_id = null; if ( is_int( $post_id ) ) { return $post_id; } return $post_id = apply_filters( 'et_core_page_resource_current_post_id', get_the_ID() ); } endif; if ( ! function_exists( 'et_core_page_resource_is_singular' ) ): function et_core_page_resource_is_singular() { return apply_filters( 'et_core_page_resource_is_singular', is_singular() ); } endif; if ( ! function_exists( 'et_debug' ) ): function et_debug( $msg, $bt_index = 4, $log_ajax = true ) { ET_Core_Logger::debug( $msg, $bt_index, $log_ajax ); } endif; if ( ! function_exists( 'et_wrong' ) ): function et_wrong( $msg, $error = false ) { $msg = "You're Doing It Wrong! {$msg}"; if ( $error ) { et_error( $msg ); } else { et_debug( $msg ); } } endif; if ( ! function_exists( 'et_error' ) ): function et_error( $msg, $bt_index = 4 ) { ET_Core_Logger::error( "[ERROR]: {$msg}", $bt_index ); } endif; HTTPInterface.php 0000644 00000026016 15222651263 0007666 0 ustar 00 <?php /** * Simple object to hold HTTP request details. * * @since 1.1.0 * * @package ET\Core\HTTP */ class ET_Core_HTTPRequest { /** * @var array */ public $ARGS; /** * @var bool */ public $BLOCKING = true; /** * @var null|array */ public $BODY = null; /** * @var bool */ public $COMPLETE = false; /** * @var array */ public $COOKIES = array(); /** * @var array */ public $HEADERS = array(); /** * @var bool */ public $IS_AUTH = false; /** * @var string */ public $METHOD = 'GET'; /** * @var string */ public $OWNER; /** * @var string */ public $URL; /** * @var string */ public $USER_AGENT; /** * ET_Core_HTTP_Request constructor. * * @param string $url The request URL. * @param string $method HTTP request method. Default is 'GET'. * @param string $owner The name of the owner of this request instance. Default is 'ET_Core'. * @param bool $is_auth Whether or not this request is auth-related. Cache disabled if `true`. Default is `false`. * @param array $body The request body. Default is `null`. * @param bool $is_json_body */ public function __construct( $url, $method = 'GET', $owner = 'ET_Core', $is_auth = false, $body = null, $is_json_body = false, $ssl_verify = true ) { $this->URL = esc_url_raw( $url ); $this->METHOD = $method; $this->BODY = $body; $this->IS_AUTH = $is_auth; $this->OWNER = $owner; $this->data_format = null; $this->JSON_BODY = $is_json_body; $this->SSL_VERIFY = $ssl_verify; $this->_set_user_agent(); $this->prepare_args(); } /** * Only include necessary properties when printing this object using {@link var_dump}. * * @return array */ public function __debugInfo() { return array( 'ARGS' => $this->ARGS, 'URL' => $this->URL, 'METHOD' => $this->METHOD, 'BODY' => $this->BODY, 'IS_AUTH' => $this->IS_AUTH, 'IS_JSON_BODY' => $this->JSON_BODY, 'OWNER' => $this->OWNER, ); } /** * Sets the user agent string. */ private function _set_user_agent() { global $wp_version; $owner = $this->OWNER; $version = 'bloom' === $owner ? $GLOBALS['et_bloom']->plugin_version : ET_CORE_VERSION; if ( 'builder' === $owner ) { $owner = 'Divi Builder'; } if ( '' === $owner ) { $this->OWNER = $owner = 'ET_Core'; } else { $owner = ucfirst( $owner ); } $this->USER_AGENT = "WordPress/{$wp_version}; {$owner}/{$version}; " . esc_url_raw( get_bloginfo( 'url' ) ); } /** * Prepares the request arguments (to be passed to wp_remote_*()) */ public function prepare_args() { $this->ARGS = array( 'blocking' => $this->BLOCKING, 'body' => $this->BODY, 'cookies' => $this->COOKIES, 'headers' => $this->HEADERS, 'method' => $this->METHOD, 'sslverify' => $this->SSL_VERIFY, 'user-agent' => $this->USER_AGENT, 'timeout' => 30, ); } } /** * Simple object to hold HTTP response details. * * @since 1.1.0 * * @package ET\Core\HTTP */ class ET_Core_HTTPResponse { /** * @var array */ public $COOKIES; /** * @var string|array */ public $DATA; /** * @var bool */ public $ERROR = false; /** * The error message if `self::$ERROR` is `true`. * * @var string */ public $ERROR_MESSAGE; /** * @var array */ public $HEADERS; /** * @var array|WP_Error */ public $RAW_RESPONSE; /** * @var ET_Core_HTTPRequest */ public $REQUEST; /** * The response's HTTP status code. * * @var int */ public $STATUS_CODE; /** * @var string */ public $STATUS_MESSAGE; /** * ET_Core_HTTP_Response constructor. * * @param ET_Core_HTTPRequest $request * @param array|WP_Error $response */ public function __construct( $request, $response ) { $this->REQUEST = $request; $this->RAW_RESPONSE = $response; $this->_parse_response(); } /** * Parse response and save relevant details. */ private function _parse_response() { if ( is_wp_error( $this->RAW_RESPONSE ) ) { $this->ERROR = true; $this->ERROR_MESSAGE = $this->RAW_RESPONSE->get_error_message(); $this->STATUS_CODE = $this->RAW_RESPONSE->get_error_code(); $this->STATUS_MESSAGE = $this->ERROR_MESSAGE; return; } $this->DATA = $this->RAW_RESPONSE['body']; $this->HEADERS = $this->RAW_RESPONSE['headers']; $this->COOKIES = $this->RAW_RESPONSE['cookies']; $this->STATUS_CODE = $this->RAW_RESPONSE['response']['code']; $this->STATUS_MESSAGE = $this->RAW_RESPONSE['response']['message']; if ( $this->STATUS_CODE >= 400 ) { $this->ERROR = true; $this->ERROR_MESSAGE = $this->STATUS_MESSAGE; } } /** * Only include necessary properties when printing this object using {@link var_dump}. * * @return array */ public function __debugInfo() { return array( 'STATUS_CODE' => $this->STATUS_CODE, 'STATUS_MESSAGE' => $this->STATUS_MESSAGE, 'ERROR' => $this->ERROR, 'ERROR_MESSAGE' => $this->ERROR_MESSAGE, 'DATA' => $this->DATA, ); } /** * Only include necessary properties when serializing this object for * storage in the WP Transient Cache. * * @return array */ public function __sleep() { return array( 'ERROR', 'ERROR_MESSAGE', 'STATUS_CODE', 'STATUS_MESSAGE', 'DATA' ); } } /** * High level, generic, wrapper for making HTTP requests. It uses WordPress HTTP API under-the-hood. * * @since 1.1.0 * * @package ET\Core\HTTP */ class ET_Core_HTTPInterface { /** * How much time responses are cached (in seconds). * * @since 1.1.0 * @var int */ protected $cache_timeout; /** * @var ET_Core_HTTPRequest */ public $request; /** * @var ET_Core_HTTPResponse */ public $response; /** * Whether or not json responses are expected to be received. Default is `true`. * * @var bool */ public $expects_json; /** * The name of the theme/plugin that created this class instance. Default: 'ET_Core'. * * @var string */ public $owner; /** * ET_Core_API_HTTP_Interface constructor. * * @since 1.1.0 * * @param string $owner The name of the theme/plugin that created this class instance. Default: 'ET_Core'. * @param array $request_details Array of config values for the request. Optional. * @param bool $json Whether or not json responses are expected to be received. Default is `true`. */ public function __construct( $owner = 'ET_Core', $request_details = array(), $json = true ) { $this->expects_json = $json; $this->cache_timeout = 15 * MINUTE_IN_SECONDS; $this->owner = $owner; if ( ! empty( $request_details ) ) { list( $url, $method, $is_auth, $body ) = $request_details; $this->prepare_request( $url, $method, $is_auth, $body ); } } /** * Only include necessary properties when printing this object using {@link var_dump}. * * @return array */ public function __debugInfo() { return array( 'REQUEST' => $this->request, 'RESPONSE' => $this->response, ); } /** * Only include necessary properties when serializing this object for * storage in the WP Transient Cache. * * @return array */ public function __sleep() { return array( 'request', 'response' ); } /** * Creates an identifier key for a request based on the URL and body content. * * @internal * @since 1.1.0 * * @param string $url The request URL. * @param string|string[] $body The request body. * * @return string */ protected static function _get_cache_key_for_request( $url, $body ) { if ( is_array( $body ) ) { $url .= json_encode( $body ); } else if ( ! empty( $body ) ) { $url .= $body; } return 'et-core-http-response-' . md5( $url ); } /** * Writes request/response info to the error log for failed requests. * * @internal * @since 1.1.0 */ protected function _log_failed_request() { $details = print_r( $this, true ); $class_name = get_class( $this ); $msg_part = "{$class_name} ERROR :: Remote request failed...\n\n"; $msg = "{$msg_part}Details: {$details}"; $max_len = @ini_get( 'log_errors_max_len' ); @ini_set( 'log_errors_max_len', 0 ); ET_Core_Logger::error( $msg ); if ( $max_len ) { @ini_set( 'log_errors_max_len', $max_len ); } } /** * Prepares request to send JSON data. */ protected function _setup_json_request() { $this->request->HEADERS['Accept'] = 'application/json'; if ( $this->request->JSON_BODY ) { $this->request->HEADERS['Content-Type'] = 'application/json'; $is_json = is_string( $this->request->BODY ) && in_array( $this->request->BODY[0], array( '[', '{' ) ); if ( $is_json || null === $this->request->BODY ) { return; } $this->request->BODY = json_encode( $this->request->BODY ); } } /** * Performs a remote HTTP request. Responses are cached for {@see self::$cache_timeout} seconds using * the {@link https://goo.gl/c0FSMH WP Transients API}. * * @since 1.1.0 */ public function make_remote_request() { $response = null; if ( $this->expects_json && ! isset( $this->request->HEADERS['Content-Type'] ) ) { $this->_setup_json_request(); } // Make sure we include any changes made after request object was instantiated. $this->request->prepare_args(); if ( 'POST' === $this->request->METHOD ) { $response = wp_remote_post( $this->request->URL, $this->request->ARGS ); } else if ( 'GET' === $this->request->METHOD && null === $this->request->data_format ) { $response = wp_remote_get( $this->request->URL, $this->request->ARGS ); } else if ( 'GET' === $this->request->METHOD && null !== $this->request->data_format ) { // WordPress sends data as query args for GET and HEAD requests and provides no way // to alter that behavior. Thus, we need to monkey patch it for now. See the mp'd class // for more details. require_once 'lib/WPHttp.php'; $wp_http = new ET_Core_LIB_WPHttp(); $this->request->ARGS['data_format'] = $this->request->data_format; $response = $wp_http->request( $this->request->URL, $this->request->ARGS ); } else if ( 'PUT' === $this->request->METHOD ) { $this->request->ARGS['method'] = 'PUT'; $response = wp_remote_request( $this->request->URL, $this->request->ARGS ); } $this->response = $response = new ET_Core_HTTPResponse( $this->request, $response ); if ( $response->ERROR || defined( 'ET_DEBUG' ) ) { $this->_log_failed_request(); } if ( $this->expects_json ) { $response->DATA = json_decode( $response->DATA, true ); } $this->request->COMPLETE = true; } /** * Replaces the current request object with a new instance. * * @param string $url * @param string $method * @param bool $is_auth * @param mixed? $body * @param bool $json_body * @param bool $ssl_verify */ public function prepare_request( $url, $method = 'GET', $is_auth = false, $body = null, $json_body = false, $ssl_verify = true ) { $this->request = new ET_Core_HTTPRequest( $url, $method, $this->owner, $is_auth, $body, $json_body, $ssl_verify ); } } SupportCenterMUAutoloader.php 0000644 00000003135 15222651263 0012362 0 ustar 00 <?php /** * Plugin Name: ET Support Center :: Must-Use Plugins Autoloader * Plugin URI: http://www.elegantthemes.com * Description: This plugin enables the Elegant Themes Support Center to provide more consistent functionality when Safe Mode is active. * Author: Elegant Themes * Author URI: http://www.elegantthemes.com * License: GPLv2 or later * * @package ET\Core\SupportCenter\SafeModeDisablePlugins * @author Elegant Themes <http://www.elegantthemes.com> * @license GNU General Public License v2 <http://www.gnu.org/licenses/gpl-2.0.html> */ // The general idea here is loosely based on <https://codex.wordpress.org/Must_Use_Plugins#Autoloader_Example>. // Quick exit if accessed directly if ( ! defined( 'ABSPATH' ) ) { exit; } // We only want to load these MU Plugins if Support Center is installed $support_center_installed = get_option( 'et_support_center_installed' ); if ( $support_center_installed ) { // Compile a list of plugins in the `mu-plugins/et-safe-mode` directory // (see `$pathname_to` in `ET_Core_SupportCenter::maybe_add_mu_autoloader()`) if ( $mu_plugins = glob( dirname( __FILE__ ) . '/et-safe-mode/*.php' ) ) { // Verbose logging: only log if `wp-config.php` has defined `ET_DEBUG='support_center'` $DEBUG_ET_SUPPORT_CENTER = defined( 'ET_DEBUG' ) && 'support_center' === ET_DEBUG; // Loop through the list of plugins and require each in turn foreach ( $mu_plugins as $plugin ) { if ( file_exists( $plugin ) ) { if ( $DEBUG_ET_SUPPORT_CENTER ) { error_log( 'ET Support Center: loading mu-plugin: ' . $plugin ); } require_once( $plugin ); } } } } README.md 0000644 00000001646 15222651263 0006036 0 ustar 00 ## Core Components This directory contains all of Core's PHP Components. Components are organized into groups using a nested directory structure. ### What's Inside |File|Name|Description| |:--------|:----------|:----------| |**api**|**ET_Core_API**|Communicate with 3rd-party APIs| |**data**|**ET_Core_Data**|Work with data (consume, format, etc)| |**lib**|**ET_Core_LIB**|3rd-Party Libraries| |**post**|**ET_Core_Post**|Work with WP Objects| |Cache.php|ET_Core_Cache|Simple object cache| |HTTPInterface.php|ET_Core_HTTPInterface|Wrapper for WP's HTTP API| |Logger.php|ET_Core_Logger|Write to the debug log. |PageResource.php|ET_Core_PageResource|Cache inline styles & scripts as static files.| |Portability.php|ET_Core_Portability|Portability import/export| |Rollback.php|ET_Core_VersionRollback|Theme & plugin version rollback| |Updates.php|ET_Core_Updates|Theme & plugin updates| > ***Note:*** Component groups are in **bold**. PageResource.php 0000644 00000103473 15222651263 0007655 0 ustar 00 <?php /** * Manages a frontend inline CSS or JavaScript resource. * * If possible, the resource will be served as a static file for better performance. It can be * tied to a specific post or it can be 'global'. The resource can be output, static or inline, * to one of four locations on the page: * * * `head-early`: right AFTER theme styles have been enqueued * * `head` : right BEFORE the theme and wp's inline custom css * * `head-late` : right AFTER the theme and wp's inline custom css * * `footer` : in the footer * * The first time the class is instantiated, a static callback method will be registered for each * output location. Inside each callback, we'll iterate over any/all instances that are assigned * to the current output location and perform the following steps: * * 1. If a static file exists for the resource, go to the next step. Otherwise, try to create * a static file for the resource if it has `data`. If it doesn't have `data`, assign it to * the next output location and then move on to the next resource (continue). * 2. If a static file exists for the resource, enqueue it (via WP or manually) and then move on * to the next resource (continue). If no static file exists, go to the next step. * 3. Output the resource inline. * * @since 2.0 * * @package ET\Core */ class ET_Core_PageResource { /** * Lock file. * * @var string[] */ protected static $_lock_file; /** * Onload attribute for stylesheet output. * * @var string[] */ private static $_onload = ''; /** * Output locations. * * @var string[] */ protected static $_output_locations = array( 'head-early', 'head', 'head-late', 'footer', ); /** * Resource owners. * * @var string[] */ protected static $_owners = array( 'divi', 'builder', 'epanel', 'epanel_temp', 'extra', 'core', 'bloom', 'monarch', 'custom', 'all', ); /** * Resource scopes. * * @var string[] */ protected static $_scopes = array( 'global', 'post', ); /** * Temp DIRS. * * @var array */ protected static $_temp_dirs = array(); /** * Resource types. * * @var string[] */ protected static $_types = array( 'style', 'script', ); /** * Whether or not we have write access to the filesystem. * * @var bool */ protected static $_can_write; /** * Request ID. * * @var int */ protected static $_request_id; /** * Request time. * * @var string */ protected static $_request_time; /** * All instances of this class. * * @var ET_Core_PageResource[] { * * @type ET_Core_PageResource $slug * } */ protected static $_resources; /** * All instances of this class organized by output location and sorted by priority. * * @var array[] { * * @type array[] $location {@see self::$_output_locations} { * * @type ET_Core_PageResource[] $priority { * * @type ET_Core_PageResource $slug * } * } * } */ protected static $_resources_by_location; /** * All instances of this class organized by scope. * * @var array[] { * * @type ET_Core_PageResource[] $post|$global { * * @type ET_Core_PageResource $slug * } * } */ protected static $_resources_by_scope; /** * @var string */ public static $WP_CONTENT_DIR; /** * @var string */ public static $current_output_location; /** * @var ET_Core_Data_Utils */ public static $data_utils; /** * @var \WP_Filesystem_Base|null */ public static $wpfs; /** * The absolute path to the directory where the static resource will be stored. * * @var string */ public $base_dir; /** * The absolute path to the static resource on the server. * * @var string */ public $path; /** * Temp DIR. * * @var array */ public $temp_dir; /** * The absolute URL through which the static resource can be downloaded. * * @var string */ public $url; /** * The data/contents for/of the static resource sorted by priority. * * @var array */ public $data; /** * Whether or not this resource has been disabled. * * @var bool */ public $disabled; /** * Whether or not the static resource file has been enqueued. * * @var bool */ public $enqueued; /** * Whether or not this resource is forced inline. * * @var bool */ public $forced_inline; /** * @var string */ public $filename; /** * Whether or not the resource has already been output to the page inline. * * @var bool */ public $inlined; /** * The owner of this instance. * * @var string */ public $owner; /** * The id of the post to which this resource belongs. * * @var string */ public $post_id; /** * The priority of this resource. * * @var int */ public $priority; /** * A unique identifier for this resource. * * @var string */ public $slug; /** * The resource type (style|script). * * @var string */ public $type; /** * The output location during which this resource's static file should be generated. * * @var string */ public $write_file_location; /** * The output location where this resource should be output. * * @var string */ public $location; /** * ET_Core_PageResource constructor * * @param string $owner The owner of the instance (core|divi|builder|bloom|monarch|custom). * @param string $slug A string that uniquely identifies the resource. * @param string|int $post_id The post id that the resource is associated with or `global`. * If `null`, {@link get_the_ID()} will be used. * @param string $type The resource type (style|script). Default: `style`. * @param string $location Where the resource should be output (head|footer). Default: `head`. */ public function __construct( $owner, $slug, $post_id = null, $priority = 10, $location = 'head-late', $type = 'style' ) { $this->owner = self::_validate_property( 'owner', $owner ); $this->post_id = self::_validate_property( 'post_id', $post_id ? $post_id : et_core_page_resource_get_the_ID() ); $this->type = self::_validate_property( 'type', $type ); $this->location = self::_validate_property( 'location', $location ); $this->write_file_location = $this->location; $this->filename = sanitize_file_name( "et-{$this->owner}-{$slug}-{$post_id}" ); $this->slug = "{$this->filename}-cached-inline-{$this->type}s"; $this->data = array(); $this->priority = $priority; self::startup(); $this->_initialize_resource(); } /** * Activates the class */ public static function startup() { if ( null !== self::$_resources ) { // Class has already been initialized return; } $time = (string) microtime( true ); $time = str_replace( '.', '', $time ); $rand = (string) mt_rand(); self::$_request_time = $time; self::$_request_id = "{$time}-{$rand}"; self::$_resources = array(); self::$data_utils = new ET_Core_Data_Utils(); foreach ( self::$_output_locations as $location ) { self::$_resources_by_location[ $location ] = array(); } foreach ( self::$_scopes as $scope ) { self::$_resources_by_scope[ $scope ] = array(); } // phpcs:enable self::$WP_CONTENT_DIR = self::$data_utils->normalize_path( WP_CONTENT_DIR ); self::$_lock_file = self::$_request_id . '~'; self::_register_callbacks(); self::_setup_wp_filesystem(); self::$_can_write = et_core_cache_dir()->can_write; } /** * Cleanup and save */ public static function shutdown() { if ( ! self::$_resources || ! self::$_can_write ) { return; } // Remove any leftover temporary directories that belong to this request foreach ( self::$_temp_dirs as $temp_directory ) { if ( file_exists( $temp_directory . '/' . self::$_lock_file ) ) { @self::$wpfs->delete( $temp_directory, true ); } } // Reset $_resources property; Mostly useful for unit test big request which needs to make // each test*() method act like it is different page request self::$_resources = null; if ( et_()->WPFS()->exists( self::$WP_CONTENT_DIR . '/cache/et' ) ) { // Remove old cache directory et_()->WPFS()->rmdir( self::$WP_CONTENT_DIR . '/cache/et', true ); } } protected static function _assign_output_location( $location, $resource ) { $priority_existed = isset( self::$_resources_by_location[ $location ][ $resource->priority ] ); self::$_resources_by_location[ $location ][ $resource->priority ][ $resource->slug ] = $resource; if ( ! $priority_existed ) { // We've added a new priority to the list, so put them back in sorted order. ksort( self::$_resources_by_location[ $location ], SORT_NUMERIC ); } } /** * Enqueues static file for provided script resource. * * @param ET_Core_PageResource $resource page resources. */ protected static function _enqueue_script( $resource ) { // Bust PHP's stats cache for the resource file to ensure we get the latest timestamp. clearstatcache( true, $resource->path ); $can_enqueue = 0 === did_action( 'wp_print_scripts' ); $timestamp = filemtime( $resource->path ); if ( $can_enqueue ) { wp_enqueue_script( $resource->slug, set_url_scheme( $resource->url ), array(), $timestamp, true ); } else { $timestamp = $timestamp ? $timestamp : ET_CORE_VERSION; printf( '<script id="%1$s" src="%2$s"></script>', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript esc_attr( $resource->slug ), esc_url( set_url_scheme( $resource->url . "?ver={$timestamp}" ) ) ); } $resource->enqueued = true; } /** * Enqueues static file for provided style resource. * * @param ET_Core_PageResource $resource */ protected static function _enqueue_style( $resource ) { if ( 'footer' === self::$current_output_location ) { return; } // Bust PHP's stats cache for the resource file to ensure we get the latest timestamp. clearstatcache( true, $resource->path ); $can_enqueue = 0 === did_action( 'wp_print_scripts' ); // reason: We do this on purpose when a style can't be enqueued. // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet $template = '<link rel="stylesheet" id="%1$s" href="%2$s" />'; // phpcs:enable $timestamp = filemtime( $resource->path ); if ( $can_enqueue ) { wp_enqueue_style( $resource->slug, set_url_scheme( $resource->url ), array(), $timestamp ); } else { // reason: this whole file needs to be converted. // phpcs:disable ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase $timestamp = $timestamp ?: ET_CORE_VERSION; $slug = esc_attr( $resource->slug ); $scheme = esc_url( set_url_scheme( $resource->url . "?ver={$timestamp}" ) ); $tag = sprintf( $template, $slug, $scheme ); $onload = et_core_esc_previously( self::$_onload ); // phpcs:enable $tag = apply_filters( 'et_core_page_resource_tag', $tag, $slug, $scheme, $onload ); print( et_core_esc_previously( $tag ) ); } $resource->enqueued = true; } /** * Returns the next output location. * * @see self::$_output_locations * * @return string */ protected static function _get_next_output_location() { $current_index = array_search( self::$current_output_location, self::$_output_locations, true ); if ( false === $current_index || ! is_int( $current_index ) ) { ET_Core_Logger::error( '$current_output_location is invalid!' ); } $current_index += 1; return self::$_output_locations[ $current_index ]; } /** * Creates static resource files for an output location if needed. * * @param string $location {@link self::$_output_locations}. */ protected static function _maybe_create_static_resources( $location ) { self::$current_output_location = $location; // Disable for footer inside builder if page uses Theme Builder Editor to avoid conflict with critical CSS. if ( 'footer' === $location && et_core_is_fb_enabled() && et_fb_is_theme_builder_used_on_page() ) { return false; } $sorted_resources = self::get_resources_by_output_location( $location ); foreach ( $sorted_resources as $priority => $resources ) { foreach ( $resources as $slug => $resource ) { if ( $resource->write_file_location !== $location ) { // This resource's static file needs to be generated later on. self::_assign_output_location( $resource->write_file_location, $resource ); continue; } if ( ! self::$_can_write ) { // The reason we don't simply check this before looping through resources and // bail if it fails is because we need to perform the output location assignment // in the previous conditional regardless (otherwise builder styles will break). continue; } if ( $resource->forced_inline || $resource->has_file() ) { continue; } $data = $resource->get_data( 'file' ); if ( empty( $data ) && 'footer' !== $location ) { // This resource doesn't have any data yet so we'll assign it to the next output location. $next_location = self::_get_next_output_location(); $resource->set_output_location( $next_location ); continue; } $force_write = apply_filters( 'et_core_page_resource_force_write', false, $resource ); if ( ! $force_write && empty( $data ) ) { continue; } // Make sure directory exists. if ( ! self::$data_utils->ensure_directory_exists( $resource->base_dir ) ) { self::$_can_write = false; return; } $can_continue = true; // Try to create a temporary directory which we'll use as a pseudo file lock if ( @mkdir( $resource->temp_dir, 0755 ) ) { //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Just ignore this since it's an internal use. self::$_temp_dirs[] = $resource->temp_dir; // Make sure another request doesn't delete our temp directory. $lock_file = $resource->temp_dir . '/' . self::$_lock_file; self::$wpfs->put_contents( $lock_file, '' ); // Create the static resource file if ( ! self::$wpfs->put_contents( $resource->path, $data, 0644 ) ) { // There's no point in continuing self::$_can_write = $can_continue = false; } else { // Remove the temporary directory self::$wpfs->delete( $resource->temp_dir, true ); /** * Fires when the static resource file is created. * * @since 4.10.8 * * @param object $resource The resource object. */ do_action( 'et_core_static_file_created', $resource ); } } elseif ( file_exists( $resource->temp_dir ) ) { // The static resource file is currently being created by another request continue; } else { // Failed for some other reason. There's no point in continuing self::$_can_write = $can_continue = false; return; } if ( ! $can_continue ) { return; } if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } } } } /** * Enqueues static files for an output location if available. * * @param string $location {@link self::$_output_locations}. */ protected static function _maybe_enqueue_static_resources( $location ) { $sorted_resources = self::get_resources_by_output_location( $location ); foreach ( $sorted_resources as $priority => $resources ) { foreach ( $resources as $slug => $resource ) { if ( $resource->disabled ) { // Resource is disabled. Remove it from the queue. self::_unassign_output_location( $location, $resource ); continue; } if ( $resource->forced_inline || ! $resource->url || ! $resource->has_file() ) { continue; } if ( 'style' === $resource->type ) { self::_enqueue_style( $resource ); } elseif ( 'script' === $resource->type ) { self::_enqueue_script( $resource ); } if ( $resource->enqueued ) { self::_unassign_output_location( $location, $resource ); } } } } /** * Outputs all non-enqueued resources for an output location inline. * * @param string $location {@link self::$_output_locations}. */ protected static function _maybe_output_inline_resources( $location ) { $sorted_resources = self::get_resources_by_output_location( $location ); foreach ( $sorted_resources as $priority => $resources ) { foreach ( $resources as $slug => $resource ) { if ( $resource->disabled ) { // Resource is disabled. Remove it from the queue. self::_unassign_output_location( $location, $resource ); continue; } $data = $resource->get_data( 'inline' ); $same_write_file_location = $resource->write_file_location === $resource->location; if ( empty( $data ) && 'footer' !== $location && $same_write_file_location ) { // This resource doesn't have any data yet so we'll assign it to the next output location. $next_location = self::_get_next_output_location(); $resource->set_output_location( $next_location ); continue; } elseif ( empty( $data ) ) { continue; } printf( '<%1$s id="%2$s">%3$s</%1$s>', esc_html( $resource->type ), esc_attr( $resource->slug ), et_core_esc_previously( wp_strip_all_tags( $data ) ) ); if ( $same_write_file_location ) { // File wasn't created during this location's callback and it won't be created later $resource->inlined = true; } } } } /** * Registers necessary callbacks. */ protected static function _register_callbacks() { $class = 'ET_Core_PageResource'; // Output Location: head-early, right after theme styles have been enqueued. add_action( 'wp_enqueue_scripts', array( $class, 'head_early_output_cb' ), 11 ); // Output Location: head, right BEFORE the theme and wp's custom css. add_action( 'wp_head', array( $class, 'head_output_cb' ), 99 ); // Output Location: head-late, right AFTER the theme and wp's custom css. add_action( 'wp_head', array( $class, 'head_late_output_cb' ), 103 ); // Output Location: footer. add_action( 'wp_footer', array( $class, 'footer_output_cb' ), 20 ); // Always delete cached resources for a post upon saving. add_action( 'save_post', array( $class, 'save_post_cb' ), 10, 3 ); // Always delete cached resources for theme customizer upon saving. add_action( 'customize_save_after', array( $class, 'customize_save_after_cb' ) ); /* * Always delete dynamic css when saving widgets. * `widget_update_callback` fires on save for any of the present widgets, * `delete_widget` fires on save for any deleted widget. */ add_filter( 'widget_update_callback', array( $class, 'widget_update_callback_cb' ) ); add_filter( 'delete_widget', array( $class, 'widget_update_callback_cb' ) ); } /** * Initializes the WPFilesystem class. */ protected static function _setup_wp_filesystem() { // The wpfs instance will always exists at this point because the cache dir class initializes it beforehand self::$wpfs = $GLOBALS['wp_filesystem']; } /** * Unassign a resource from an output location. * * @param string $location {@link self::$_output_locations}. * @param ET_Core_PageResource $resource */ protected static function _unassign_output_location( $location, $resource ) { unset( self::$_resources_by_location[ $location ][ $resource->priority ][ $resource->slug ] ); } protected static function _validate_property( $property, $value ) { $valid_values = array( 'location' => self::$_output_locations, 'owner' => self::$_owners, 'type' => self::$_types, ); switch ( $property ) { case 'path': $value = et_()->normalize_path( realpath( $value ) ); $is_valid = et_()->starts_with( $value, et_core_cache_dir()->path ); break; case 'url': $base_url = et_core_cache_dir()->url; $is_valid = et_()->starts_with( $value, set_url_scheme( $base_url, 'http' ) ); $is_valid = $is_valid ? $is_valid : et_()->starts_with( $value, set_url_scheme( $base_url, 'https' ) ); break; case 'post_id': $is_valid = 'global' === $value || 'all' === $value || is_numeric( $value ); break; default: $is_valid = isset( $valid_values[ $property ] ) && in_array( $value, $valid_values[ $property ] ); break; } return $is_valid ? $value : ''; } /** * Whether or not we are able to write to the filesystem. * * @return bool */ public static function can_write_to_filesystem() { return ET_Core_Cache_Directory::instance()->can_write; } /** * Output Location: footer * {@see 'wp_footer' (20) Allow third-party extensions some room to do what they do} */ public static function footer_output_cb() { self::_maybe_create_static_resources( 'footer' ); self::_maybe_enqueue_static_resources( 'footer' ); self::_maybe_output_inline_resources( 'footer' ); } /** * Returns the absolute path to our cache directory. * * @since 4.0.8 Removed `$path_type` param b/c cache directory might not be located under wp-content. * @since 3.0.52 * * @return string */ public static function get_cache_directory() { return et_core_cache_dir()->path; } /** * Returns all current resources. * * @return array {@link self::$_resources} */ public static function get_resources() { return self::$_resources; } /** * Returns the current resources for the provided output location, sorted by priority. * * @param string $location The desired output location {@see self::$_output_locations}. * * @return array[] { * * @type ET_Core_PageResource[] $priority { * * @type ET_Core_PageResource $slug Resource. * ... * } * ... * } */ public static function get_resources_by_output_location( $location ) { return self::$_resources_by_location[ $location ]; } /** * Returns the current resources for the provided scope. * * @param string $scope The desired scope (post|global). * * @return ET_Core_PageResource[] */ public static function get_resources_by_scope( $scope ) { return self::$_resources_by_scope[ $scope ]; } /** * Output Location: head-early * {@see 'wp_enqueue_scripts' (11) Should run right after the theme enqueues its styles.} */ public static function head_early_output_cb() { self::_maybe_create_static_resources( 'head-early' ); self::_maybe_enqueue_static_resources( 'head-early' ); self::_maybe_output_inline_resources( 'head-early' ); } /** * Output Location: head * {@see 'wp_head' (99) Must run BEFORE the theme and WP's custom css callbacks.} */ public static function head_output_cb() { self::_maybe_create_static_resources( 'head' ); self::_maybe_enqueue_static_resources( 'head' ); self::_maybe_output_inline_resources( 'head' ); } /** * Output Location: head-late * {@see 'wp_head' (103) Must run AFTER the theme and WP's custom css callbacks.} */ public static function head_late_output_cb() { self::_maybe_create_static_resources( 'head-late' ); self::_maybe_enqueue_static_resources( 'head-late' ); self::_maybe_output_inline_resources( 'head-late' ); } /** * {@see 'widget_update_callback'} * * @param array $instance Widget settings being saved. */ public static function widget_update_callback_cb( $instance ) { self::remove_static_resources( 'all', 'all', false, 'dynamic' ); return $instance; } /** * {@see 'customize_save_after'} * * @param WP_Customize_Manager $manager */ public static function customize_save_after_cb( $manager ) { self::remove_static_resources( 'all', 'all' ); } /** * {@see 'save_post'} * * @param int $post_id * @param WP_Post $post * @param bool $update */ public static function save_post_cb( $post_id, $post, $update ) { // In Dynamic CSS, we parse the layout content for generating styles and store it under the `object_id`, so clearing // only the layout assets won't update the page style if we made any changes to the layout/global modules etc. // Hence, we need to clear all static resources when we update a layout. // Also, we should only clear the cache if the layout being saved is a global module/row/section. if ( 'et_pb_layout' === $post->post_type ) { $taxonomies = get_taxonomies( [ 'object_type' => [ 'et_pb_layout' ] ] ); $tax_to_clear = array( 'scope', 'layout_type' ); $types_to_clear = array( 'module', 'row', 'section' ); $scope_terms = get_the_terms( $post_id, 'scope' ); $layout_terms = get_the_terms( $post_id, 'layout_type' ); if ( ! empty( $scope_terms ) && ! empty( $layout_terms ) ) { $scope_terms = wp_list_pluck( $scope_terms, 'slug' ); $layout_terms = wp_list_pluck( $layout_terms, 'slug' ); $is_global = in_array( 'global', $scope_terms, true ); $clearable_modules = array_intersect( $types_to_clear, $layout_terms ); $remove_resource = $is_global && ! empty( $clearable_modules ); foreach ( $taxonomies as $taxonomy ) { if ( in_array( $taxonomy, $tax_to_clear, true ) && $remove_resource ) { $post_id = 'all'; break; } } } } self::remove_static_resources( $post_id, 'all' ); } /** * Remove static resources for a post, or optionally all resources, if any exist. * * @param string $post_id id of post. * @param string $owner owner of file. * @param bool $force remove all resources. * @param string $slug file slug. */ public static function remove_static_resources( $post_id, $owner = 'core', $force = false, $slug = 'all' ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( ! wp_doing_cron() && ! et_core_security_check_passed( 'edit_posts' ) ) { return; } if ( ! self::can_write_to_filesystem() ) { return; } if ( ! self::$data_utils ) { self::startup(); } self::do_remove_static_resources( $post_id, $owner, $force, $slug ); } /** * Remove static resources action. * * @param string $post_id id of post. * @param string $owner owner of file. * @param bool $force remove all resources. * @param string $slug file slug. */ public static function do_remove_static_resources( $post_id, $owner = 'core', $force = false, $slug = 'all' ) { $post_id = self::_validate_property( 'post_id', $post_id ); $owner = self::_validate_property( 'owner', $owner ); $slug = sanitize_key( $slug ); if ( '' === $owner || '' === $post_id ) { return; } $_post_id = 'all' === $post_id ? '*' : $post_id; $_owner = 'all' === $owner ? '*' : $owner; $_slug = 'all' === $slug ? '*' : $slug; $cache_dir = self::get_cache_directory(); $files = array_merge( // Remove any CSS files missing a parent folder. (array) glob( "{$cache_dir}/et-{$_owner}-*" ), // Remove CSS files for individual posts or all posts if $post_id set to 'all'. (array) glob( "{$cache_dir}/{$_post_id}/et-{$_owner}-{$_slug}*" ), // Remove CSS files that contain theme builder template CSS. // Multiple directories need to be searched through since * doesn't match / in the glob pattern. (array) glob( "{$cache_dir}/*/et-{$_owner}-{$_slug}-*tb-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/*/et-{$_owner}-{$_slug}-*tb-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/*/*/et-{$_owner}-{$_slug}-*tb-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/et-{$_owner}-{$_slug}-*tb-for-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/*/et-{$_owner}-{$_slug}-*tb-for-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/*/*/et-{$_owner}-{$_slug}-*tb-for-{$_post_id}*" ), // Remove Dynamic CSS files for categories, tags, authors, archives, homepage post feed and search results. (array) glob( "{$cache_dir}/taxonomy/*/*/et-{$_owner}-dynamic*" ), (array) glob( "{$cache_dir}/author/*/et-{$_owner}-dynamic*" ), (array) glob( "{$cache_dir}/archive/et-{$_owner}-dynamic*" ), (array) glob( "{$cache_dir}/search/et-{$_owner}-dynamic*" ), (array) glob( "{$cache_dir}/notfound/et-{$_owner}-dynamic*" ), (array) glob( "{$cache_dir}/home/et-{$_owner}-dynamic*" ), // WP Templates and Template Parts. (array) glob( "{$cache_dir}/*/et-{$_owner}-{$_slug}-*wpe-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/*/et-{$_owner}-{$_slug}-*wpe-{$_post_id}*" ), (array) glob( "{$cache_dir}/*/*/*/et-{$_owner}-{$_slug}-*wpe-{$_post_id}*" ) ); self::_remove_files_in_directory( $files, $cache_dir ); // Remove empty directories. self::$data_utils->remove_empty_directories( $cache_dir ); // Clear cache managed by 3rd-party cache plugins. $post_id = ! empty( $post_id ) && absint( $post_id ) > 0 ? $post_id : ''; et_core_clear_wp_cache( $post_id ); // Purge the module features cache. if ( class_exists( 'ET_Builder_Module_Features' ) ) { if ( ! empty( $post_id ) ) { ET_Builder_Module_Features::purge_cache( $post_id ); } else { ET_Builder_Module_Features::purge_cache(); } } // Purge the google fonts cache. if ( empty( $post_id ) && class_exists( 'ET_Builder_Google_Fonts_Feature' ) ) { ET_Builder_Google_Fonts_Feature::purge_cache(); } // Purge the dynamic assets cache. if ( empty( $post_id ) && class_exists( 'ET_Builder_Dynamic_Assets_Feature' ) ) { ET_Builder_Dynamic_Assets_Feature::purge_cache(); } $post_meta_caches = array( 'et_enqueued_post_fonts', '_et_dynamic_cached_shortcodes', '_et_dynamic_cached_attributes', '_et_builder_module_features_cache', ); // Clear post meta caches. foreach ( $post_meta_caches as $post_meta_cache ) { if ( ! empty( $post_id ) ) { delete_post_meta( $post_id, $post_meta_cache ); } else { delete_post_meta_by_key( $post_meta_cache ); } } // Set our DONOTCACHEPAGE file for the next request. self::$data_utils->ensure_directory_exists( $cache_dir ); self::$wpfs->put_contents( $cache_dir . '/DONOTCACHEPAGE', '' ); if ( $force ) { delete_option( 'et_core_page_resource_remove_all' ); } /** * Fires when the static resources are removed. * * @since 4.21.1 * * @param mixed $post_id The post ID. */ do_action( 'et_core_static_resources_removed', $post_id ); } /** * Removes a list of files from the designated directory. * * @param array[] $files List of patterns to match. * @param string $cache_dir Cache directory. */ protected static function _remove_files_in_directory( $files, $cache_dir ) { foreach ( $files as $file ) { $file = self::$data_utils->normalize_path( $file ); if ( ! et_()->starts_with( $file, $cache_dir ) ) { // File is not located inside cache directory so skip it. continue; } if ( is_file( $file ) ) { self::$wpfs->delete( $file ); } } } public static function wpfs() { if ( null !== self::$wpfs ) { return self::$wpfs; } self::startup(); return self::$wpfs = et_core_cache_dir()->wpfs; } protected function _initialize_resource() { if ( ! self::can_write_to_filesystem() ) { $this->base_dir = $this->temp_dir = $this->path = $this->url = ''; //phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found -- Just ignore this since it's an internal use. $this->_register_resource(); return; } $file_extension = 'style' === $this->type ? '.min.css' : '.min.js'; $path = self::get_cache_directory(); $url = et_core_cache_dir()->url; $file = et_()->path( $path, $this->post_id, $this->filename . $file_extension ); if ( file_exists( $file ) ) { // Static resource file exists $this->path = self::$data_utils->normalize_path( $file ); $this->base_dir = dirname( $this->path ); $this->url = et_()->path( $url, $this->post_id, basename( $this->path ) ); } else { // Static resource file doesn't exist $url .= "/{$this->post_id}/{$this->filename}{$file_extension}"; $path .= "/{$this->post_id}/{$this->filename}{$file_extension}"; $this->base_dir = self::$data_utils->normalize_path( dirname( $path ) ); $this->temp_dir = $this->base_dir . "/{$this->slug}~"; $this->path = $path; $this->url = $url; } $this->_register_resource(); } protected function _register_resource() { $this->enqueued = false; $this->inlined = false; $scope = 'global' === $this->post_id ? 'global' : 'post'; self::$_resources[ $this->slug ] = $this; self::$_resources_by_scope[ $scope ][ $this->slug ] = $this; self::_assign_output_location( $this->location, $this ); } public function get_data( $context ) { $result = ''; ksort( $this->data, SORT_NUMERIC ); /** * Filters the resource's data array. * * @since 3.0.52 * * @param array[] $data { * * @type string[] $priority Resource data. * ... * } * @param string $context Where the data will be used. Accepts 'inline', 'file'. * @param ET_Core_PageResource $resource The resource instance. */ $resource_data = apply_filters( 'et_core_page_resource_get_data', $this->data, $context, $this ); foreach ( $resource_data as $priority => $data_part ) { foreach ( $data_part as $data ) { $result .= $data; } } return $result; } /** * Whether or not a static resource exists on the filesystem for this instance. * * @return bool */ public function has_file() { if ( ! self::$wpfs || empty( $this->path ) || ! self::can_write_to_filesystem() ) { return false; } return self::$wpfs->exists( $this->path ); } /** * Set the resource's data. * * @param string $data * @param int $priority */ public function set_data( $data, $priority = 10 ) { if ( 'style' === $this->type ) { $data = et_core_data_utils_minify_css( $data ); // Remove empty media queries // @media only..and (feature:value) { } $pattern = '/@media\s+([\w\s]+)?\([\w-]+:[\w\d-]+\)\{\s*\}/'; $data = preg_replace( $pattern, '', $data ); } $this->data[ $priority ][] = trim( strip_tags( str_replace( '\n', '', $data ) ) ); } public function set_output_location( $location ) { if ( ! self::_validate_property( 'location', $location ) ) { return; } $current_location = $this->location; self::_unassign_output_location( $current_location, $this ); self::_assign_output_location( $location, $this ); $this->location = $location; } public function unregister_resource() { $scope = 'global' === $this->post_id ? 'global' : 'post'; unset( self::$_resources[ $this->slug ], self::$_resources_by_scope[ $scope ][ $this->slug ] ); self::_unassign_output_location( $this->location, $this ); } } SupportCenter.php 0000644 00000364371 15222651263 0010114 0 ustar 00 <?php // Quick exit if accessed directly if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Elegant Themes Support Center adds a new page to the WP Admin menu. * * System Status * Here we note system settings that could potentially cause problems. An extended view (displaying all settings we * check, not just those with problematic results) can be toggled, with an option to copy this report to the * clipboard so it can be pasted in a support ticket. * * Elegant Themes Support * If Remote Access is enabled in this section, Elegant Themes Support will be granted limited access to the user's site * (@see ET_Core_SupportCenter::support_user_maybe_create_user()). When this is activated, a second toggle appears * for the user that will allow them to enable "Full Admin Privileges" which has no restrictions (only certain ET * Support staff will be able to request that the user enables this). Full Admin Privileges can be disabled at any * time, but are automatically disabled whenever the Remote Access is disabled (manually or by timeout). Time * remaining until Remote Access is automatically deactivated is indicated alongside the toggle. A link for * initiating a chat https://www.elegantthemes.com/members-area/help/ is also available in this section. * * Divi Documentation & Help * This section contains common help videos, articles, and a link to full documentation. This is not meant to be a * full service documentation center; it's mainly a launch off point. * * Divi Safe Mode * A quick and easy way for users and support to quickly disable plugins and scripts to see if Divi is the cause of * an issue. This call to action disables active plugins, custom css, child themes, scripts in the Integrations tab, * static css, and combination/minification of CSS and JS. When enabling this, the user will be presented with a * list of plugins that will be affected (disabled). Sitewide (not including the Visual Builder), there will be a * floating indicator in the upper right or left corner of the website that will indicate that Safe Mode is enabled * and will contain a link that takes you to the Support Page to disabled it. * * Logs * If WP_DEBUG_LOG is enabled, WordPress related errors will be archived in a log file. We load the most recent entries * of this log file for convienient viewing here, with a link to download the full log, as well as an option to copy * the log to the clipboard so it can be pasted in a support ticket. * * @package ET\Core\SupportCenter * @author Elegant Themes <http://www.elegantthemes.com> * @license GNU General Public License v2 <http://www.gnu.org/licenses/gpl-2.0.html> * * @since 3.24.1 Renamed from `ET_Support_Center` to `ET_Core_SupportCenter`. * @since 3.20 */ class ET_Core_SupportCenter { /** * Catch whether the ET_DEBUG flag is set. * * @since 3.20 * * @type string */ protected $DEBUG_ET_SUPPORT_CENTER = false; /** * Identifier for the parent theme or plugin activating the Support Center. * * @since 3.20 * * @type string */ protected $parent = ''; /** * "Nice name" for the parent theme or plugin activating the Support Center. * * @since 3.20 * * @type string */ protected $parent_nicename = ''; /** * Whether the Support Center was activated through a `plugin` or a `theme`. * * @since 3.20 * * @type string */ protected $child_of = ''; /** * Identifier for the parent theme or plugin activating the Support Center. * * @since 3.20 * * @type string */ protected $local_path; /** * Support User options * * @since 3.20 * * @type array */ protected $support_user_options; /** * Support User account name * * @since 3.20 * * @type string */ protected $support_user_account_name = 'elegant_themes_support'; /** * Support options name in the database * * @since 3.20 * * @type string */ protected $support_user_options_name = 'et_support_options'; /** * Name of the cron job we use to auto-delete the Support User account * * @since 3.20 * * @type string */ protected $support_user_cron_name = 'et_cron_delete_support_account'; /** * Expiration time to auto-delete the Support User account via cron * * @since 3.20 * * @type string */ protected $support_user_expiration_time = '+4 days'; /** * Provide nicename equivalents for boolean values * * @since 3.23 * * @type array */ protected $boolean_label = array( 'False', 'True' ); /** * Collection of plugins that we will NOT disable when Safe Mode is activated. * * @since 3.20 * * @type array */ protected $safe_mode_plugins_allowlist = array( 'etdev/etdev.php', // ET Development Workspace 'bloom/bloom.php', // ET Bloom Plugin 'monarch/monarch.php', // ET Monarch Plugin 'divi-builder/divi-builder.php', // ET Divi Builder Plugin 'divi-dash/divi-dash.php', // Divi Dash Plugin 'ari-adminer/ari-adminer.php', // ARI Adminer 'query-monitor/query-monitor.php', // Query Monitor 'woocommerce/woocommerce.php', // WooCommerce 'really-simple-ssl/rlrsssl-really-simple-ssl.php', // Really Simple SSL ); /** * Capabilities that should be granted to the Administrator role on activation. * * @since 3.28 * * @type array */ protected $support_center_administrator_caps = array( 'et_support_center', 'et_support_center_system', 'et_support_center_remote_access', 'et_support_center_documentation', 'et_support_center_safe_mode', 'et_support_center_logs', ); /** * Collection of Cards that has button to dismiss the Card. * * @since 4.4.7 * * @type array */ protected $card_with_dismiss_button = array( 'et_hosting_card', ); /** * Core functionality of the class * * @since 4.4.7 Added WP AJAX action for dismissible button for a card * @since 3.20 * * @param string $parent Identifier for the parent theme or plugin activating the Support Center. */ public function __construct( $parent = '' ) { // Verbose logging: only log if `wp-config.php` has defined `ET_DEBUG='support_center'` $this->DEBUG_ET_SUPPORT_CENTER = defined( 'ET_DEBUG' ) && 'support_center' === ET_DEBUG; // Set the identifier for the parent theme or plugin activating the Support Center. $this->parent = $parent; // Get `et_support_options` settings & set $this->support_user_options $this->support_user_get_options(); // Set the plugins allowlist for Safe Mode $this->set_safe_mode_plugins_allowlist(); } /** * WordPress action & filter setup * * @since 3.20 */ public function init() { update_option( 'et_support_center_installed', 'true' ); // Establish which theme or plugin has loaded the Support Center $this->set_parent_properties(); // When initialized, deactivate conflicting plugins $this->deactivate_conflicting_plugins(); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts_styles' ) ); // SC scripts are only used in FE for the "Turn Off Divi Safe Mode" floating button. if ( et_core_is_safe_mode_active() ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts_styles' ) ); } // Get Site ID with Elegant Themes API & token (needed in advance for Remote Access). add_action( 'admin_init', array( $this, 'maybe_set_site_id' ) ); // Add extra User Role capabilities needed for Remote Access to work with 3rd party software add_filter( 'add_et_support_standard_capabilities', array( $this, 'support_user_extra_caps_standard' ), 10, 1 ); add_filter( 'add_et_support_elevated_capabilities', array( $this, 'support_user_extra_caps_elevated' ), 10, 1 ); // Make sure that our Support Account's roles are set up add_filter( 'add_et_builder_role_options', array( $this, 'support_user_add_role_options' ), 10, 1 ); // On Multisite installs, grant `unfiltered_html` capabilities to the Support User add_filter( 'map_meta_cap', array( $this, 'support_user_map_meta_cap' ), 1, 3 ); // Add CSS class name(s) to the Support Center page's body tag add_filter( 'admin_body_class', array( $this, 'add_admin_body_class_name' ) ); // Add a link to the Support Center in the admin menu add_filter( 'admin_menu', array( $this, 'add_admin_menu_item' ) ); // When Safe Mode is enabled, add floating frontend indicator add_action( 'admin_footer', array( $this, 'maybe_add_safe_mode_indicator' ) ); add_action( 'wp_footer', array( $this, 'maybe_add_safe_mode_indicator' ) ); // Add User capabilities to the Administrator Role add_action( 'admin_init', array( $this, 'support_center_capabilities_setup' ) ); if ( 'plugin' === $this->child_of ) { // Delete our Support User settings on deactivation register_deactivation_hook( __FILE__, array( $this, 'support_user_delete_account' ) ); register_deactivation_hook( __FILE__, array( $this, 'unlist_support_center' ) ); register_deactivation_hook( __FILE__, array( $this, 'support_center_capabilities_teardown' ) ); } if ( 'theme' === $this->child_of ) { // Delete our Support User settings on deactivation add_action( 'switch_theme', array( $this, 'maybe_deactivate_on_theme_switch' ) ); } // Automatically delete our Support User when the time runs out add_action( $this->support_user_cron_name, array( $this, 'support_user_cron_maybe_delete_account' ) ); add_action( 'init', array( $this, 'support_user_maybe_delete_expired_account' ) ); add_action( 'admin_init', array( $this, 'support_user_maybe_delete_expired_account' ) ); // Remove KSES filters for ET Support User add_action( 'admin_init', array( $this, 'support_user_kses_remove_filters' ) ); // Update Support User settings via AJAX add_action( 'wp_ajax_et_support_user_update', array( $this, 'support_user_update_via_ajax' ) ); // Toggle Safe Mode via AJAX add_action( 'wp_ajax_et_safe_mode_update', array( $this, 'safe_mode_update_via_ajax' ) ); // Safe Mode: Block restricted actions when Safe Mode active add_action( 'admin_footer', array( $this, 'render_safe_mode_block_restricted' ) ); // Safe Mode: Temporarily disable Custom CSS add_action( 'init', array( $this, 'maybe_disable_custom_css' ) ); // Safe Mode: Remove "Additional CSS" from WP Head action hook if ( et_core_is_safe_mode_active() ) { remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); } // Support Center Card: Handle Dismiss Button add_action( 'wp_ajax_et_dismiss_support_center_card', array( $this, 'dismiss_support_center_card_via_ajax' ) ); } /** * Add User capabilities to the Administrator Role (if it exists) on first run * * @since 3.29.2 Added check to verify the Administrator Role exists before attempting to run `add_cap()`. * @since 3.28 */ public function support_center_capabilities_setup() { $support_capabilities = get_option( 'et_support_center_setup_done' ); $administrator_role = get_role( 'administrator' ); if ( $administrator_role && ! $support_capabilities ) { foreach ( $this->support_center_administrator_caps as $cap ) { $administrator_role->add_cap( $cap ); } update_option( 'et_support_center_setup_done', 'processed' ); } } /** * Remove User capabilities from the Administrator Role when product with Support Center is removed * * @since 3.29.2 Added check to verify the Administrator Role exists before attempting to run `remove_cap()`. * @since 3.28 */ public function support_center_capabilities_teardown() { $support_capabilities = get_option( 'et_support_center_setup_done' ); $administrator_role = get_role( 'administrator' ); if ( $administrator_role && $support_capabilities ) { foreach ( $this->support_center_administrator_caps as $cap ) { $administrator_role->remove_cap( $cap ); } delete_option( 'et_support_center_setup_done' ); } } /** * Set variables that change depending on whether a theme or a plugin activated the Support Center * * @since 3.20 */ public function set_parent_properties() { $core_path = _et_core_normalize_path( trailingslashit( dirname( __FILE__ ) ) ); $theme_dir = _et_core_normalize_path( trailingslashit( realpath( get_template_directory() ) ) ); if ( 0 === strpos( $core_path, $theme_dir ) ) { $this->child_of = 'theme'; $this->local_path = trailingslashit( get_template_directory_uri() . '/core/' ); } else { $this->child_of = 'plugin'; $this->local_path = plugins_url( '/', dirname( __FILE__ ) ); } $this->parent_nicename = $this->get_parent_nicename( $this->parent ); } /** * Get the "Nice Name" for the parent theme/plugin * * @param $parent * * @return bool|string */ public function get_parent_nicename( $parent ) { switch ( $parent ) { case 'bloom_plugin': return 'Bloom'; break; case 'monarch_plugin': return 'Monarch'; break; case 'extra_theme': return 'Extra'; break; case 'divi_theme': return 'Divi'; break; case 'divi_builder_plugin': return 'Divi Builder'; break; case 'divi_dash_plugin': return 'Divi Dash'; break; default: return false; } } /** * Prevent any possible conflicts with the Elegant Themes Support plugin * * @since 3.20 */ public function deactivate_conflicting_plugins() { require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); // Load WP user management functions if ( is_multisite() ) { require_once( ABSPATH . 'wp-admin/includes/ms.php' ); } else { require_once( ABSPATH . 'wp-admin/includes/user.php' ); } // Verify that WP user management functions are available $can_delete_user = false; if ( is_multisite() && function_exists( 'wpmu_delete_user' ) ) { $can_delete_user = true; } if ( ! is_multisite() && function_exists( 'wp_delete_user' ) ) { $can_delete_user = true; } if ( $can_delete_user ) { deactivate_plugins( '/elegant-themes-support/elegant-themes-support.php' ); } else { et_error( 'Support Center: Unable to deactivate the ET Support Plugin.' ); } } /** * @param string $capability * * @return bool */ protected function current_user_can( $capability = '' ) { if ( function_exists( 'et_is_builder_plugin_active' ) ) { return et_pb_is_allowed( $capability ); } return current_user_can( $capability ); } /** * Add Safe Mode Autoloader Must-Use Plugin * * @since 3.20 */ public function maybe_add_mu_autoloader() { $file_name = '/SupportCenterMUAutoloader.php'; $file_path = dirname( __FILE__ ); // Exit if the `mu-plugins` directory doesn't exist & we're unable to create it if ( ! wp_mkdir_p( WPMU_PLUGIN_DIR ) ) { et_error( 'Support Center Safe Mode: mu-plugin folder not found.' ); return; } $pathname_to = WPMU_PLUGIN_DIR . $file_name; $pathname_from = $file_path . $file_name; // Exit if we can't find the mu-plugins autoloader if ( ! file_exists( $pathname_from ) ) { et_error( 'Support Center Safe Mode: mu-plugin autoloader not found.' ); return; } // Try to create a new subdirectory for our mu-plugins; if it fails, log an error message $pathname_plugins_from = dirname( __FILE__ ) . '/mu-plugins'; $pathname_plugins_to = WPMU_PLUGIN_DIR . '/et-safe-mode'; if ( ! wp_mkdir_p( $pathname_plugins_to ) ) { et_error( 'Support Center Safe Mode: mu-plugins subfolder not found.' ); return; } // Try to copy the mu-plugins; if any fail, log an error message if ( $mu_plugins = glob( dirname( __FILE__ ) . '/mu-plugins/*.php' ) ) { foreach ( $mu_plugins as $plugin ) { $new_file_path = str_replace( $pathname_plugins_from, $pathname_plugins_to, $plugin ); // Skip if this particular mu-plugin hasn't changed if ( file_exists( $new_file_path ) && md5_file( $new_file_path ) === md5_file( $plugin ) ) { continue; } $copy_file = @copy( $plugin, $new_file_path ); if ( ! $this->DEBUG_ET_SUPPORT_CENTER ) { continue; } if ( $copy_file ) { et_error( 'Support Center Safe Mode: mu-plugin [' . $plugin . '] installed.' ); } else { et_error( 'Support Center Safe Mode: mu-plugin [' . $plugin . '] failed installation. ' ); } } } // Finally, try to copy the autoloader file; if it fails, log an error message // Skip if the mu-plugins autoloader hasn't changed if ( file_exists( $pathname_to ) && md5_file( $pathname_to ) === md5_file( $pathname_from ) ) { return; } $copy_file = @copy( $pathname_from, $pathname_to ); if ( $this->DEBUG_ET_SUPPORT_CENTER ) { if ( $copy_file ) { et_error( 'Support Center Safe Mode: mu-plugin installed.' ); } else { et_error( 'Support Center Safe Mode: mu-plugin failed installation. ' ); } } } public function maybe_remove_mu_autoloader() { @unlink( WPMU_PLUGIN_DIR . '/SupportCenterMUAutoloader.php' ); @unlink( WPMU_PLUGIN_DIR . '/et-safe-mode/SupportCenterSafeModeDisablePlugins.php' ); et_()->remove_empty_directories( WPMU_PLUGIN_DIR . '/et-safe-mode' ); } /** * Update the Site ID data via Elegant Themes API * * @since ?.? Early exit if no Site ID, but also no API credentials to use for a request. * @since 3.20 * * @return void */ public function maybe_set_site_id() { // Early exit if the user doesn't have Support Center access. if ( ! $this->current_user_can( 'et_support_center' ) ) { return; } $site_id = get_option( 'et_support_site_id' ); // If we already have a saved Site ID for support, then we don't need to request a new ID. if ( ! empty( $site_id ) ) { return; } // If there are no saved API credentials, then we can't use the API to request a Site ID for support. if ( ! $this->get_et_license() ) { return; } $site_id = ''; $send_to_api = array( 'action' => 'get_site_id', ); $settings = array( 'timeout' => 30, 'body' => $send_to_api, ); $request = wp_remote_post( 'https://www.elegantthemes.com/api/token.php', $settings ); if ( ! is_wp_error( $request ) && 200 == wp_remote_retrieve_response_code( $request ) ) { $response = unserialize( wp_remote_retrieve_body( $request ) ); if ( ! empty( $response['site_id'] ) ) { $site_id = esc_attr( $response['site_id'] ); } } update_option( 'et_support_site_id', $site_id ); } /** * Safe Mode temporarily deactivates all plugins *except* those in the allowlist option set here * * @since 3.20 * * @return void */ public function set_safe_mode_plugins_allowlist() { update_option( 'et_safe_mode_plugins_allowlist', $this->safe_mode_plugins_allowlist ); } /** * Add Support Center menu item (but only if it's enabled for current user) * * When initialized we were given an identifier for the plugin or theme doing the initializing. We're going to use * that identifier here to insert the Support Center menu item in the correct location within the WP Admin Menu. * * @since 3.28 Expanded sub-menu links with support for additional ET products. * @since 3.20 */ public function add_admin_menu_item() { // Early exit if the user doesn't have Support Center access if ( ! $this->current_user_can( 'et_support_center' ) ) { return; } $menu_title = esc_html__( 'Support Center', 'et-core' ); $menu_slug = null; $parent_menu_slug = null; // By default, only user with `manage_options` capability which is "administrator" // can see Support Center menu and access the page. $capability = 'manage_options'; // Define parent and child menu slugs switch ( $this->parent ) { case 'bloom_plugin': $menu_slug = 'et_support_center_bloom'; $parent_menu_slug = 'et_bloom_options'; break; case 'monarch_plugin': $menu_title = esc_html__( 'Monarch Support Center', 'et-core' ); $menu_slug = 'et_support_center_monarch'; $parent_menu_slug = 'tools.php'; break; case 'extra_theme': $menu_slug = 'et_support_center_extra'; $parent_menu_slug = 'et_extra_options'; break; case 'divi_theme': case 'divi_builder_plugin': // In the Roles Editor, other user roles may have access to the Support Center. // But, most likely they don't have `manage_options` capability. So, if current // user can't `manage_options`, we will set the submenu capability into the // `edit_theme_options`, so other roles with `edit_theme_options` capability // can access the Support Center. if ( ! current_user_can( 'manage_options' ) ) { $capability = 'edit_theme_options'; } // However, that's not enough. We still need to check whether current user with // `manage_options` or `edit_theme_options` is allowed to access Support Center. if ( ! et_pb_is_allowed( 'support_center' ) ) { return; } $menu_slug = 'et_support_center_divi'; $parent_menu_slug = 'et_divi_options'; } // If there's no menu slug, then this product doesn't have Support Center enabled if ( ! $menu_slug ) { return; } // Build the link add_submenu_page( $parent_menu_slug, $menu_title, $menu_title, $capability, $menu_slug, array( $this, 'add_support_center' ) ); } /** * Add class name to Support Center page * * @since 3.20 * * @param string $admin_classes Current class names for the body tag. * * @return string */ public function add_admin_body_class_name( $admin_classes = '' ) { $classes = explode( ' ', $admin_classes ); $classes[] = 'et-admin-page'; if ( et_core_is_safe_mode_active() ) { $classes[] = 'et-safe-mode-active'; } return implode( ' ', $classes ); } /** * Support Center admin page JS * * @since 3.20 * * @param $hook string Unique identifier for WP admin page. * * @return void */ public function admin_enqueue_scripts_styles( $hook ) { et_core_register_admin_assets(); wp_enqueue_style( 'et-core-admin' ); wp_enqueue_script( 'et-core-admin' ); // Load only on `_et_support_center` pages. if ( strpos( $hook, '_et_support_center' ) ) { // Core Admin CSS wp_enqueue_style( 'et-core', $this->local_path . 'admin/css/core.css', array(), ET_CORE_VERSION ); // ePanel CSS wp_enqueue_style( 'et-wp-admin', $this->local_path . 'admin/css/wp-admin.css', array(), ET_CORE_VERSION ); // Support Center CSS wp_enqueue_style( 'et-support-center', $this->local_path . 'admin/css/support-center.css', array(), ET_CORE_VERSION ); // Support Center uses ePanel controls, so include the necessary scripts if ( function_exists( 'et_core_enqueue_js_admin' ) ) { et_core_enqueue_js_admin(); } } } /** * Support Center frontend CSS/JS * * @since 3.20 * * @param $hook string Unique identifier for WP admin page. * * @return void */ public function enqueue_scripts_styles( $hook ) { // We only need to add this for authenticated users on the frontend if ( ! is_user_logged_in() ) { return; } // Support Center JS wp_enqueue_script( 'et-support-center', $this->local_path . 'admin/js/support-center.js', array( 'jquery', 'underscore' ), ET_CORE_VERSION, true ); $support_center_nonce = wp_create_nonce( 'support_center' ); $etSupportCenterSettings = array( 'ajaxLoaderImg' => esc_url( $this->local_path . 'admin/images/ajax-loader.gif' ), 'ajaxURL' => admin_url( 'admin-ajax.php' ), 'siteURL' => get_site_url(), 'supportCenterURL' => get_admin_url( null, 'admin.php?page=et_support_center#et_card_safe_mode' ), 'nonce' => $support_center_nonce, ); wp_localize_script( 'et-support-center', 'etSupportCenter', $etSupportCenterSettings ); } /** * Divi Support Center :: Card * * Take an array of attributes and build a WP Card block for display on the Divi Support Center page. * * @since 4.4.7 Added optional dismissible button * @since 3.20 * * @param array $attrs * * @return string */ protected function add_support_center_card( $attrs = array( 'title' => '', 'content' => '' ) ) { $card_classes = array( 'card', ); if ( array_key_exists( 'additional_classes', $attrs ) ) { $card_classes = array_merge( $card_classes, $attrs['additional_classes'] ); } $dismiss_button = ''; if ( array_key_exists( 'dismiss_button', $attrs ) ) { // Update card class to indicate the presence of the dismiss button $card_classes = array_merge( $card_classes, array( 'has-dismiss-button' ) ); // Prepare Class for the Dismiss button $dismiss_button_classes = array( 'et-dismiss-button' ); if ( array_key_exists( 'additional_classes', $attrs['dismiss_button'] ) ) { $dismiss_button_classes = array_merge( $dismiss_button_classes, $attrs['dismiss_button']['additional_classes'] ); } // Whether to display tooltip for the dismiss button $dismiss_button_has_tooltip = array_key_exists( 'tooltip', $attrs['dismiss_button'] ); // HTML Template for the dismiss button $dismiss_button = PHP_EOL . "\t" . sprintf( '<button class="%2$s" data-key="%3$s" data-product="%4$s" %5$s type="button" ><span class="et-dismiss-button-label">%1$s</span></button>', esc_html__( 'Dismiss', 'et-core' ), esc_attr( implode( ' ', $dismiss_button_classes ) ), esc_attr( $attrs['dismiss_button']['card_key'] ), esc_attr( $this->parent ), $dismiss_button_has_tooltip ? 'data-tippy-content="' . esc_attr( $attrs['dismiss_button']['tooltip'] ) . '"' : '' ); } $card = PHP_EOL . '<div class="' . esc_attr( implode( ' ', $card_classes ) ) . '">' . PHP_EOL . "\t" . '<h2>' . esc_html( $attrs['title'] ) . '</h2>' . PHP_EOL . "\t" . '<div class="main">' . et_core_intentionally_unescaped( $attrs['content'], 'html' ) . '</div>' . et_core_esc_previously( $dismiss_button ) . PHP_EOL . '</div>'; return $card; } /** * Divi Support Center :: Dismiss a Card via Ajax * * @since 4.4.7 */ public function dismiss_support_center_card_via_ajax() { et_core_security_check( 'manage_options', 'support_center', 'nonce' ); $response = array(); // Check the ET product that dismissing the card $et_product = sanitize_key( $_POST['product'] ); // Confirm that this is a allowlisted product $allowlisted_product = $this->is_allowlisted_product( $et_product ); if ( ! $allowlisted_product ) { // Send a failure code and exit the function header( "HTTP/1.0 403 Forbidden" ); print 'Bad or malformed ET product name.'; wp_die(); } // Check the Card key against Cards that has a dismiss button $card_key = sanitize_key( $_POST['card_key'] ); if ( ! in_array( $card_key, $this->card_with_dismiss_button, true ) ) { // Send a failure code and exit the function header( "HTTP/1.0 403 Forbidden" ); print 'Card does not exists.'; wp_die(); } // Update option(s) update_option( "{$card_key}_dismissed", true ); // For Divi Hosting Card, update the status via ET API if ( $card_key === 'et_hosting_card' ) { $settings = $this->get_et_api_request_settings( 'disable_hosting_card' ); $et_username = et_()->array_get( $settings, 'body.username', '' ); $et_api_key = et_()->array_get( $settings, 'body.api_key', '' ); // Exit if ET Username and/or ET API Key is not found if ( $et_username === '' || $et_api_key === '' ) { return; } et_maybe_update_hosting_card_status(); } $response['message'] = sprintf( esc_html__( 'Card (%1$s) has been dismissed successfully.', 'et-core' ), $card_key ); // `echo` data to return if ( isset( $response ) ) { wp_send_json_success( $response ); } // `die` when we're done wp_die(); } /** * Prepare the "Divi Documentation & Help" video player block * * @since 3.28 Added support for Bloom, Monarch, and Divi Builer plugins. * @since 3.20 * * @param bool $formatted Return either a formatted HTML block (true) or an array (false) * * @return array|string */ protected function get_documentation_video_player( $formatted = true ) { /** * Define the videos list */ switch ( $this->parent ) { case 'extra_theme': $documentation_videos = array( array( 'name' => esc_attr__( 'A Basic Overview Of Extra', 'et-core' ), 'youtube_id' => 'JDSg9eq4LIc', ), array( 'name' => esc_attr__( 'Using Premade Layout Packs', 'et-core' ), 'youtube_id' => '9eqXcrLcnoc', ), array( 'name' => esc_attr__( 'Creating Category Layouts', 'et-core' ), 'youtube_id' => '30SVxnjdnxcE', ), ); break; case 'divi_theme': case 'divi_builder_plugin': $documentation_videos = array( array( 'name' => esc_attr__( 'Getting Started With The Divi Builder', 'et-core' ), 'youtube_id' => 'T-Oe01_J62c', ), array( 'name' => esc_attr__( 'Using Premade Layout Packs', 'et-core' ), 'youtube_id' => '9eqXcrLcnoc', ), array( 'name' => esc_attr__( 'The Divi Library', 'et-core' ), 'youtube_id' => 'boNZZ0MYU0E', ), ); break; case 'bloom_plugin': $documentation_videos = array( array( 'name' => esc_attr__( 'A Basic Overview Of The Bloom Plugin', 'et-core' ), 'youtube_id' => 'E4nfXFjuRRI', ), array( 'name' => esc_attr__( 'How To Update The Bloom Plugin', 'et-core' ), 'youtube_id' => '-IIdkRLskuA', ), array( 'name' => esc_attr__( 'How To Add Mailing List Accounts', 'et-core' ), 'youtube_id' => 'nEdWkHIgQwY', ), ); break; case 'monarch_plugin': $documentation_videos = array( array( 'name' => esc_attr__( 'A Complete Overviw Of Monarch', 'et-core' ), 'youtube_id' => 'RlMUEVkbMrs', ), array( 'name' => esc_attr__( 'Adding Social Networks', 'et-core' ), 'youtube_id' => 'ZabKCiKQJLM', ), array( 'name' => esc_attr__( 'Configuring Social Follower APIs', 'et-core' ), 'youtube_id' => 'vmE8uFhbzos', ), ); break; default: $documentation_videos = array(); } // If we just want the array (not a formatted HTML block), return that now if ( false === $formatted ) { return $documentation_videos; } $videos_list_html = ''; $playlist = array(); foreach ( $documentation_videos as $key => $video ) { $extra = ''; if ( 0 === $key ) { $extra = ' class="active"'; } $videos_list_html .= sprintf( '<li %1$s data-ytid="%2$s">%3$s%4$s</li>', $extra, esc_attr( $video['youtube_id'] ), '<span class="dashicons dashicons-arrow-right"></span>', et_core_intentionally_unescaped( $video['name'], 'fixed_string' ) ); $playlist[] = et_core_intentionally_unescaped( $video['youtube_id'], 'fixed_string' ); } $html = sprintf( '<div class="et_docs_videos">' . '<div class="wrapper"><div id="et_documentation_player" data-playlist="%1$s"></div></div>' . '<ul class="et_documentation_videos_list">%2$s</ul>' . '</div>', esc_attr( implode( ',', $playlist ) ), $videos_list_html ); return $html; } /** * Prepare the "Divi Documentation & Help" articles list * * @since 3.28 Added support for Bloom, Monarch, and Divi Builer plugins. * @since 3.20 * * @param bool $formatted Return either a formatted HTML block (true) or an array (false) * * @return array|string */ protected function get_documentation_articles_list( $formatted = true ) { $articles_list_html = ''; switch ( $this->parent ) { case 'extra_theme': $articles = array( array( 'title' => esc_attr__( 'Getting Started With Extra', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/extra/overview-extra/', ), array( 'title' => esc_attr__( 'Setting Up The Extra Theme Options', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/extra/theme-options-extra/', ), array( 'title' => esc_attr__( 'The Extra Category Builder', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/extra/category-builder/', ), array( 'title' => esc_attr__( 'Getting Started With The Divi Builder', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/visual-builder/', ), array( 'title' => esc_attr__( 'How To Update The Extra Theme', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/update-divi/', ), array( 'title' => esc_attr__( 'An Overview Of All Divi Modules', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/modules/', ), array( 'title' => esc_attr__( 'Getting Started With Layout Packs', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/premade-layouts/', ), array( 'title' => esc_attr__( 'Customizing Your Header And Navigation', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/extra/theme-customizer/', ), ); break; case 'divi_theme': $articles = array( array( 'title' => esc_attr__( 'Getting Started With The Divi Builder', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/visual-builder/', ), array( 'title' => esc_attr__( 'How To Update The Divi Theme', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/update-divi/', ), array( 'title' => esc_attr__( 'An Overview Of All Divi Modules', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/modules/', ), array( 'title' => esc_attr__( 'Using The Divi Library', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/divi-library/', ), array( 'title' => esc_attr__( 'Setting Up The Divi Theme Options', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/theme-options/', ), array( 'title' => esc_attr__( 'Getting Started With Layout Packs', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/premade-layouts/', ), array( 'title' => esc_attr__( 'Customizing Your Header And Navigation', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/customizer-header/', ), array( 'title' => esc_attr__( 'Divi For Developers', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/developers/', ), ); break; case 'divi_builder_plugin': $articles = array( array( 'title' => esc_attr__( 'Getting Started With The Divi Builder', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/visual-builder/', ), array( 'title' => esc_attr__( 'How To Update The Divi Builder', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi-builder/update-divi-builder/', ), array( 'title' => esc_attr__( 'An Overview Of All Divi Modules', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/modules/', ), array( 'title' => esc_attr__( 'Using The Divi Library', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/divi-library/', ), array( 'title' => esc_attr__( 'Selling Products With Divi And WooCommerce', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/ecommerce-divi/', ), array( 'title' => esc_attr__( 'Getting Started With Layout Packs', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/premade-layouts/', ), array( 'title' => esc_attr__( 'Importing And Exporting Divi Layouts', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/divi/library-import/', ), array( 'title' => esc_attr__( 'Divi For Developers', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/developers/', ), ); break; case 'bloom_plugin': $articles = array( array( 'title' => esc_attr__( 'A Basic Overview Of The Bloom Plugin', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/overview/', ), array( 'title' => esc_attr__( 'How To Update Your Bloom Plugin', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/update/', ), array( 'title' => esc_attr__( 'Adding Email Accounts In Bloom', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/accounts/', ), array( 'title' => esc_attr__( 'Customizing Your Opt-in Designs', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/design/', ), array( 'title' => esc_attr__( 'The Different Bloom Opt-in Types', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/optin-types/', ), array( 'title' => esc_attr__( 'Using The Bloom Display Settings', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/display/', ), array( 'title' => esc_attr__( 'How To Use Triggers In Bloom', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/triggers/', ), array( 'title' => esc_attr__( 'Adding Custom Fields To Bloom Opt-in Forms', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/bloom/optin/adding-custom-fields-to-bloom-optin-forms/', ), ); break; case 'monarch_plugin': $articles = array( array( 'title' => esc_attr__( 'A Complete Overview Of The Monarch Plugin', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/overview-monarch/', ), array( 'title' => esc_attr__( 'How To Update Your Monarch WordPress Plugin', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/update-monarch/', ), array( 'title' => esc_attr__( 'Adding and Managing Social Networks', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/networks/', ), array( 'title' => esc_attr__( 'Configuring Social Network APIs', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/api/', ), array( 'title' => esc_attr__( 'Customizing The Monarch Design', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/design-monarch/', ), array( 'title' => esc_attr__( 'Viewing Your Social Stats', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/stats/', ), array( 'title' => esc_attr__( 'Using The Floating Sidebar', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/sidebar/', ), array( 'title' => esc_attr__( 'Using Popup & Flyin Triggers', 'et-core' ), 'url' => 'https://www.elegantthemes.com/documentation/monarch/triggers-monarch/', ), ); break; default: $articles = array(); } // If we just want the array (not a formatted HTML block), return that now if ( false === $formatted ) { return $articles; } foreach ( $articles as $key => $article ) { $articles_list_html .= sprintf( '<li class="et-support-center-article"><a href="%1$s" target="_blank">%2$s</a></li>', esc_url( $article['url'] ), et_core_intentionally_unescaped( $article['title'], 'fixed_string' ) ); } $html = sprintf( '<div class="et_docs_articles"><ul class="et_documentation_articles_list">%1$s</ul></div>', $articles_list_html ); return $html; } /** * Look for Elegant Themes Support Account * * @since 3.20 * * @return WP_User|false WP_User object on success, false on failure. */ public function get_et_support_user() { return get_user_by( 'slug', $this->support_user_account_name ); } /** * Look for saved Elegant Themes Username & API Key * * @since 3.20 * * @return array|false license credentials on success, false on failure. */ public function get_et_license() { /** @var array License credentials [username|api_key] */ if ( ! $et_license = get_site_option( 'et_automatic_updates_options' ) ) { $et_license = get_option( 'et_automatic_updates_options', array() ); } if ( ! et_()->array_get( $et_license, 'username' ) ) { return false; } if ( ! et_()->array_get( $et_license, 'api_key' ) ) { return false; } return $et_license; } /** * Try to load the WP debug log. If found, return the last [$lines_to_return] lines of the file and the filesize. * * @since 3.20 * * @param int $lines_to_return Number of lines to read and return from the end of the wp_debug.log file. * * @return array */ protected function get_wp_debug_log( $lines_to_return = 10 ) { $log = array( 'entries' => '', 'size' => 0, ); // Early exit: internal PHP function `file_get_contents()` appears to be on lockdown if ( ! function_exists( 'file_get_contents' ) ) { $log['error'] = esc_attr__( 'Divi Support Center :: WordPress debug log cannot be read.', 'et-core' ); if ( defined( 'ET_DEBUG' ) ) { et_error( $log['error'] ); } return $log; } // Early exit: WP_DEBUG_LOG isn't defined in wp-config.php (or it's defined, but it's empty) if ( ! defined( 'WP_DEBUG_LOG' ) || ! WP_DEBUG_LOG ) { $log['error'] = esc_attr__( 'Divi Support Center :: WordPress debug.log is not configured.', 'et-core' ); if ( defined( 'ET_DEBUG' ) ) { et_error( $log['error'] ); } return $log; } /** * WordPress 5.1 introduces the option to define a custom path for the WP_DEBUG_LOG file. * * @see wp_debug_mode() * * @since 3.20 */ if ( in_array( strtolower( (string) WP_DEBUG_LOG ), array( 'true', '1' ), true ) ) { $wp_debug_log_path = realpath( WP_CONTENT_DIR . '/debug.log' ); } else if ( is_string( WP_DEBUG_LOG ) ) { $wp_debug_log_path = realpath( WP_DEBUG_LOG ); } // Early exit: `debug.log` doesn't exist or otherwise can't be read if ( ! isset( $wp_debug_log_path ) || ! file_exists( $wp_debug_log_path ) || ! is_readable( $wp_debug_log_path ) ) { $log['error'] = esc_attr__( 'Divi Support Center :: WordPress debug log cannot be found.', 'et-core' ); if ( defined( 'ET_DEBUG' ) ) { et_error( $log['error'] ); } return $log; } /** * At this point, we know: * (1) `$wp_debug_log_path` is set, * (2) it points to a valid location, and * (3) what it points to is readable. * * Before we continue, we'll ensure `$wp_debug_log_path` does not point to a directory. */ // Early exit: debug log definition points to a directory, not a file. if ( is_dir( $wp_debug_log_path ) ) { $log['error'] = esc_attr__( 'Divi Support Center :: WordPress debug log setting points to a directory, but should point to a file.', 'et-core' ); if ( defined( 'ET_DEBUG' ) ) { et_error( $log['error'] ); } return $log; } // Load the debug.log file $file = new SplFileObject( $wp_debug_log_path ); // Get the filesize of debug.log $log['size'] = $this->get_size_in_shorthand( 0 + $file->getSize() ); // If $lines_to_return is a positive integer, fetch the last [$lines_to_return] lines of the log file $lines_to_return = (int) $lines_to_return; if ( $lines_to_return > 0 ) { $file->seek( PHP_INT_MAX ); $total_lines = $file->key(); // If the file is smaller than the number of lines requested, return the entire file. $reader = new LimitIterator( $file, max( 0, $total_lines - $lines_to_return ) ); $log['entries'] = ''; foreach ( $reader as $line ) { $log['entries'] .= $line; } } // Unload the SplFileObject $file = null; return $log; } /** * When a predefined system setting is passed to this function, it will return the observed value. * * @since 3.20 * * @param bool $formatted Whether to return a formatted report or just the data array * @param string $format Return the report as either a `div` or `plain` text (if $formatted = true) * * @return array|string */ protected function system_diagnostics_generate_report( $formatted = true, $format = 'plain' ) { /** @var array Collection of system settings to run diagnostic checks on. */ global $wp_version; global $shortname; $divi_builder_plugin_active = et_is_builder_plugin_active(); if ( $divi_builder_plugin_active ) { $options = get_option( 'et_pb_builder_options', array() ); } if ( 'divi' === $shortname ) { $performance_options_url = get_admin_url() . 'admin.php?page=et_divi_options#general-2'; $builder_options_url = get_admin_url() . 'admin.php?page=et_divi_options#builder-2'; } elseif ( 'extra' === $shortname ) { $performance_options_url = get_admin_url() . 'admin.php?page=et_extra_options#general-2'; $builder_options_url = get_admin_url() . 'admin.php?page=et_extra_options#builder-2'; } else { $performance_options_url = get_admin_url() . 'admin.php?page=et_divi_options#tab_et_dashboard_tab_content_performance_main'; $builder_options_url = get_admin_url() . 'admin.php?page=et_divi_options#tab_et_dashboard_tab_content_advanced_main'; } $system_diagnostics_settings = array( array( 'name' => esc_attr__( 'Writable wp-content Directory', 'et-core' ), 'environment' => 'server', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'minimum' => null, 'recommended' => true, 'actual' => wp_is_writable( WP_CONTENT_DIR ), 'help_text' => et_core_intentionally_unescaped( __( 'We recommend that the wp-content directory on your server be writable by WordPress in order to ensure the full functionality of Divi Builder themes and plugins.', 'et-core' ), 'html' ), 'learn_more' => 'https://wordpress.org/support/article/changing-file-permissions/', ), array( 'name' => esc_attr__( 'Writable et-cache Directory', 'et-core' ), 'environment' => 'server', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'minimum' => null, 'recommended' => true, 'actual' => wp_is_writable( WP_CONTENT_DIR . '/et-cache' ), 'help_text' => et_core_intentionally_unescaped( __( 'We recommend that the et-cache directory on your server be writable by WordPress in order to ensure the full functionality of Divi Builder themes and plugins.', 'et-core' ), 'html' ), 'learn_more' => 'https://wordpress.org/support/article/changing-file-permissions/', ), array( 'name' => esc_attr__( 'PHP: Version', 'et-core' ), 'environment' => 'server', 'type' => 'version', 'pass_minus_one' => false, 'pass_zero' => false, 'minimum' => null, 'recommended' => '7.4 or higher', 'actual' => (float) phpversion(), 'help_text' => et_core_intentionally_unescaped( __( 'We recommend using the latest stable version of PHP. This will not only ensure compatibility with Divi, but it will also greatly speed up your website leading to less memory and CPU related issues.', 'et-core' ), 'html' ), 'learn_more' => 'http://php.net/releases/', ), array( 'name' => esc_attr__( 'WordPress Version', 'et-core' ), 'environment' => 'server', 'type' => 'version', 'pass_minus_one' => false, 'pass_zero' => false, 'minimum' => null, 'recommended' => '5.3 or higher', 'actual' => $wp_version, 'help_text' => et_core_intentionally_unescaped( __( 'We recommend using the latest stable version of WordPress. This will not only ensure compatibility with Divi, but it will also greatly speed up your website leading to less memory and CPU related issues.', 'et-core' ), 'html' ), 'learn_more' => 'https://wordpress.org/download/releases/', ), array( 'name' => esc_attr__( 'PHP: memory_limit', 'et-core' ), 'environment' => 'server', 'type' => 'size', 'pass_minus_one' => true, 'pass_zero' => false, 'minimum' => null, 'recommended' => '128M', 'actual' => ini_get( 'memory_limit' ), 'help_text' => et_get_safe_localization( sprintf( __( 'By default, memory limits set by your host or by WordPress may be too low. This will lead to applications crashing as PHP reaches the artificial limit. You can adjust your memory limit within your <a href="%1$s" target="_blank">php.ini file</a>, or by contacting your host for assistance. You may also need to define a memory limited in <a href="%2$s" target=_blank">wp-config.php</a>.', 'et-core' ), 'http://php.net/manual/en/ini.core.php#ini.memory-limit', 'https://codex.wordpress.org/Editing_wp-config.php' ) ), 'learn_more' => 'http://php.net/manual/en/ini.core.php#ini.memory-limit', ), array( 'name' => esc_attr__( 'PHP: post_max_size', 'et-core' ), 'environment' => 'server', 'type' => 'size', 'pass_minus_one' => false, 'pass_zero' => true, 'minimum' => null, 'recommended' => '64M', 'actual' => ini_get( 'post_max_size' ), 'help_text' => et_get_safe_localization( sprintf( __( 'Post Max Size limits how large a page or file can be on your website. If your page is larger than the limit set in PHP, it will fail to load. Post sizes can become quite large when using the Divi Builder, so it is important to increase this limit. It also affects file size upload/download, which can prevent large layouts from being imported into the builder. You can adjust your max post size within your <a href="%1$s" target="_blank">php.ini file</a>, or by contacting your host for assistance.', 'et_core' ), 'http://php.net/manual/en/ini.core.php#ini.post-max-size' ) ), 'learn_more' => 'http://php.net/manual/en/ini.core.php#ini.post-max-size', ), array( 'name' => esc_attr__( 'PHP: max_execution_time', 'et-core' ), 'environment' => 'server', 'type' => 'seconds', 'pass_minus_one' => false, 'pass_zero' => true, 'minimum' => null, 'recommended' => '120', 'actual' => ini_get( 'max_execution_time' ), 'help_text' => et_get_safe_localization( sprintf( __( 'Max Execution Time affects how long a page is allowed to load before it times out. If the limit is too low, you may not be able to import large layouts and files into the builder. You can adjust your max execution time within your <a href="%1$s">php.ini file</a>, or by contacting your host for assistance.', 'et-core' ), 'http://php.net/manual/en/info.configuration.php#ini.max-execution-time' ) ), 'learn_more' => 'http://php.net/manual/en/info.configuration.php#ini.max-execution-time', ), array( 'name' => esc_attr__( 'PHP: upload_max_filesize', 'et-core' ), 'environment' => 'server', 'type' => 'size', 'pass_minus_one' => false, 'pass_zero' => true, 'minimum' => null, 'recommended' => '64M', 'actual' => ini_get( 'upload_max_filesize' ), 'help_text' => et_get_safe_localization( sprintf( __( 'Upload Max File Size determines that maximum file size that you are allowed to upload to your server. If the limit is too low, you may not be able to import large collections of layouts into the Divi Library. You can adjust your max file size within your <a href="%1$s" target="_blank">php.ini file</a>, or by contacting your host for assistance.', 'et-core' ), 'http://php.net/manual/en/ini.core.php#ini.upload-max-filesize' ) ), 'learn_more' => 'http://php.net/manual/en/ini.core.php#ini.upload-max-filesize', ), array( 'name' => esc_attr__( 'PHP: max_input_time', 'et-core' ), 'environment' => 'server', 'type' => 'seconds', 'pass_minus_one' => true, 'pass_zero' => true, 'minimum' => null, 'recommended' => '60', 'actual' => ini_get( 'max_input_time' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This sets the maximum time in seconds a script is allowed to parse input data. If the limit is too low, the Divi Builder may time out before it is allowed to load. You can adjust your max input time within your <a href="%1$s" target="_blank">php.ini file</a>, or by contacting your host for assistance.', 'et-core' ), 'http://php.net/manual/en/info.configuration.php#ini.max-input-time' ) ), 'learn_more' => 'http://php.net/manual/en/info.configuration.php#ini.max-input-time', ), array( 'name' => esc_attr__( 'PHP: max_input_vars', 'et-core' ), 'environment' => 'server', 'type' => 'size', 'pass_minus_one' => false, 'pass_zero' => false, 'minimum' => null, 'recommended' => '1000', 'actual' => ini_get( 'max_input_vars' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This setting affects how many input variables may be accepted. If the limit is too low, it may prevent the Divi Builder from loading. You can adjust your max input variables within your <a href="%1$s" target="_blank">php.ini file</a>, or by contacting your host for assistance.', 'et-core' ), 'http://php.net/manual/en/info.configuration.php#ini.max-input-vars' ) ), 'learn_more' => 'http://php.net/manual/en/info.configuration.php#ini.max-input-vars', ), array( 'name' => esc_attr__( 'PHP: display_errors', 'et-core' ), 'environment' => 'server', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'pass_exact' => null, 'minimum' => null, 'recommended' => '0', 'actual' => ! ini_get( 'display_errors' ) ? '0' : ini_get( 'display_errors' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This setting determines whether or not errors should be printed as part of the page output. This is a feature to support your site\'s development and should never be used on production sites. You can edit this setting within your <a href="%1$s" target="_blank">php.ini file</a>, or by contacting your host for assistance.', 'et-core' ), 'http://php.net/manual/en/errorfunc.configuration.php#ini.display-errors' ) ), 'learn_more' => 'http://php.net/manual/en/errorfunc.configuration.php#ini.display-errors', ), array( 'name' => esc_attr__( 'Dynamic CSS', 'et-core' ), 'environment' => 'performance', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'pass_exact' => null, 'minimum' => null, 'recommended' => 'on', 'actual' => $divi_builder_plugin_active ? ( isset( $options['performance_main_dynamic_css'] ) ? $options['performance_main_dynamic_css'] : 'on' ) : et_get_option( $shortname . '_dynamic_css', 'on' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This is a very important performance setting that should be turned on. Dynamic CSS greatly reduces your website\'s CSS size, speeds up page load times and improves Google PageSpeed scores. You can turn this setting on in the <a href="%1$s" target="_blank">Theme Options</a>.', 'et-core' ), $performance_options_url ) ), 'learn_more' => $performance_options_url, ), array( 'name' => esc_attr__( 'Dynamic Framework', 'et-core' ), 'environment' => 'performance', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'pass_exact' => null, 'minimum' => null, 'recommended' => 'on', 'actual' => $divi_builder_plugin_active ? ( isset( $options['performance_main_dynamic_module_framework'] ) ? $options['performance_main_dynamic_module_framework'] : 'on' ) : et_get_option( $shortname . '_dynamic_module_framework', 'on' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This is a very important performance setting that should be turned on. The Dynamic Framework removes bloat from the back-end. This greatly reduces CPU and Memory usage and improves website speed. You can turn this setting on in the <a href="%1$s" target="_blank">Theme Options</a>.', 'et-core' ), $performance_options_url ) ), 'learn_more' => $performance_options_url, ), array( 'name' => esc_attr__( 'Dynamic JavaScript', 'et-core' ), 'environment' => 'performance', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'pass_exact' => null, 'minimum' => null, 'recommended' => 'on', 'actual' => $divi_builder_plugin_active ? ( isset( $options['performance_main_dynamic_css'] ) ? $options['performance_main_dynamic_css'] : 'on' ) : et_get_option( $shortname . '_dynamic_js_libraries', 'on' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This is a very important performance setting that should be turned on. Dynamic JavaScript removes unused scripts and improves website speed by loading JavaScript files only when they are needed. You can turn this setting on in the <a href="%1$s" target="_blank">Theme Options</a>.', 'et-core' ), $performance_options_url ) ), 'learn_more' => $performance_options_url, ), array( 'name' => esc_attr__( 'Critical CSS', 'et-core' ), 'environment' => 'performance', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'pass_exact' => null, 'minimum' => null, 'recommended' => 'on', 'actual' => $divi_builder_plugin_active ? ( isset( $options['performance_main_critical_css'] ) ? $options['performance_main_dynamic_css'] : 'on' ) : et_get_option( $shortname . '_critical_css', 'on' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This is a very important performance setting that should be turned on. Critical CSS greatly improves website loading speeds by deferring "below the fold" styles and removing render blocking requests for critical styles. You can turn this setting on in the <a href="%1$s" target="_blank">Theme Options</a>.', 'et-core' ), $performance_options_url ) ), 'learn_more' => $performance_options_url, ), array( 'name' => esc_attr__( 'Static CSS', 'et-core' ), 'environment' => 'performance', 'type' => 'truthy_falsy', 'pass_minus_one' => null, 'pass_zero' => null, 'pass_exact' => null, 'minimum' => null, 'recommended' => 'on', 'actual' => et_get_option( 'et_pb_static_css_file', 'on' ), 'help_text' => et_get_safe_localization( sprintf( __( 'This is a very important performance setting that should be turned on, even if you are using a caching plugin. Static CSS caches the builder CSS for each page so that it doesn\'t need to be processed on every page load. Even if you are using a caching plugin, this setting should still be turned on so that dynamic pages benefit. You can turn this setting on in the <a href="%1$s" target="_blank">Theme Options</a>.', 'et-core' ), $builder_options_url ) ), 'learn_more' => $builder_options_url, ), ); /** @var string Formatted report. */ $report = ''; // pass/fail Should be one of pass|minimal|fail|unknown. Defaults to 'unknown'. foreach ( $system_diagnostics_settings as $i => $scan ) { /** * 'pass_fail': four-step process to set its value: * - begin with `unknown` state; * - if recommended value exists, change to `fail`; * - if minimum value exists, compare against it & change to `minimal` if it passes; * - compare against recommended value & change to `pass` if it passes. */ $system_diagnostics_settings[ $i ]['pass_fail'] = 'unknown'; if ( ! is_null( $scan['recommended'] ) ) { $system_diagnostics_settings[ $i ]['pass_fail'] = 'fail'; } if ( ! is_null( $scan['minimum'] ) && $this->value_is_at_least( $scan['minimum'], $scan['actual'], $scan['type'] ) ) { $system_diagnostics_settings[ $i ]['pass_fail'] = 'minimal'; } if ( empty( $scan['pass_exact'] ) && ! is_null( $scan['recommended'] ) && $this->value_is_at_least( $scan['recommended'], $scan['actual'], $scan['type'] ) ) { $system_diagnostics_settings[ $i ]['pass_fail'] = 'pass'; } if ( $scan['pass_minus_one'] && -1 === (int) $scan['actual'] ) { $system_diagnostics_settings[ $i ]['pass_fail'] = 'pass'; } if ( $scan['pass_zero'] && 0 === (int) $scan['actual'] ) { $system_diagnostics_settings[ $i ]['pass_fail'] = 'pass'; } if ( ! empty( $scan['pass_exact'] ) && $scan['recommended'] === $scan['actual'] ) { $system_diagnostics_settings[ $i ]['pass_fail'] = 'pass'; } /** * Build messaging for minimum required values */ $message_minimum = ''; if ( ! is_null( $scan['minimum'] ) && 'fail' === $system_diagnostics_settings[ $i ]['pass_fail'] ) { $message_minimum = sprintf( esc_html__( 'This fails to meet our minimum required value of %1$s. ', 'et-core' ), esc_html( is_bool( $scan['minimum'] ) ? $this->boolean_label[ $scan['minimum'] ] : $scan['minimum'] ) ); } if ( ! is_null( $scan['minimum'] ) && 'minimal' === $system_diagnostics_settings[ $i ]['pass_fail'] ) { $message_minimum = sprintf( esc_html__( 'This meets our minimum required value of %1$s. ', 'et-core' ), esc_html( is_bool( $scan['minimum'] ) ? $this->boolean_label[ $scan['minimum'] ] : $scan['minimum'] ) ); } /** * Build description messaging for results & recommendation */ $learn_more_link = ''; if ( ! is_null( $scan['learn_more'] ) ) { $learn_more_link = sprintf( ' <a href="%1$s" target="_blank">%2$s</a>', esc_url( $scan['learn_more'] ), esc_html__( 'server' === $scan['environment'] ? 'Learn More.' : 'Enable Option.', 'et-core' ) ); } switch ( $system_diagnostics_settings[ $i ]['pass_fail'] ) { case 'pass': $system_diagnostics_settings[ $i ]['description'] = sprintf( '- %1$s %2$s', sprintf( esc_html__( 'performance' === $scan['environment'] ? 'Perfect! We recommend enabling this option.' : 'Congratulations! This meets or exceeds our recommendation of %1$s.', 'et-core' ), esc_html( is_bool( $scan['recommended'] ) ? $this->boolean_label[ $scan['recommended'] ] : $scan['recommended'] ) ), et_core_intentionally_unescaped( $learn_more_link, 'html' ) ); break; case 'minimal': case 'fail': $system_diagnostics_settings[ $i ]['description'] = sprintf( '- %1$s%2$s %3$s', esc_html( $message_minimum ), sprintf( esc_html__( 'performance' === $scan['environment'] ? 'Enable for optimal performance.' : 'We recommend %1$s for the best experience.', 'et-core' ), esc_html( is_bool( $scan['recommended'] ) ? $this->boolean_label[ $scan['recommended'] ] : $scan['recommended'] ) ), et_core_intentionally_unescaped( $learn_more_link, 'html' ) ); break; case 'unknown': default: $system_diagnostics_settings[ $i ]['description'] = sprintf( esc_html__( '- We are unable to determine your setting. %1$s', 'et-core' ), et_core_intentionally_unescaped( $learn_more_link, 'html' ) ); } } // If we just want the array (not a formatted HTML block), return that now if ( false === $formatted ) { return $system_diagnostics_settings; } foreach ( $system_diagnostics_settings as $item ) { // Add reported setting to plaintext report: if ( 'plain' === $format ) { switch ( $item['pass_fail'] ) { case 'pass': $status = ' '; break; case 'minimal': $status = '~ '; break; case 'fail': $status = "! "; break; case 'unknown': default: $status = '? '; } $report .= $status . $item['name'] . PHP_EOL . ' ' . $item['actual'] . PHP_EOL . PHP_EOL; } // Add reported setting to table: if ( 'div' === $format ) { $help_text = ''; if ( ! is_null( $item['help_text'] ) ) { $help_text = $item['help_text']; } $report .= sprintf( '<div class="et-epanel-box et_system_status_row et_system_status_%1$s"> <div class="et-box-title setting"> <h3>%2$s</h3> <div class="et-box-descr"><p>%3$s</p></div> </div> <div class="et-box-content results"> <span class="actual">%4$s</span> <span class="description">%5$s</span> </div> <span class="et-box-description"></span> </div>', esc_attr( $item['pass_fail'] ), esc_html( $item['name'] ), et_core_intentionally_unescaped( $help_text, 'html' ), esc_html( is_bool( $item['actual'] ) ? $this->boolean_label[ $item['actual'] ] : $item['actual'] ), et_core_intentionally_unescaped( $item['description'], 'html' ) ); } } // Prepend title and timestamp if ( 'plain' === $format ) { $report = '## ' . esc_html__( 'System Status', 'et-core' ) . ' ##' . PHP_EOL . ':: ' . date( 'Y-m-d @ H:i:s e' ) . PHP_EOL . PHP_EOL . $report; } if ( 'div' === $format ) { $report = sprintf( '<div class="%3$s-report">%1$s</div><p class="%3$s-congratulations">%2$s</p>', $report, esc_html__( 'Congratulations, all system checks have passed. Your hosting configuration is compatible with Divi.', 'et-core' ), 'et-system-status' ); } return $report; } /** * Convert size string with "shorthand byte" notation to raw byte value for comparisons. * * @since 3.20 * * @param string $size * * @return int size in bytes */ protected function get_size_in_bytes( $size = '' ) { // Capture the denomination and convert to uppercase, then do math to it switch ( strtoupper( substr( $size, -1 ) ) ) { // Terabytes case 'T': return (int) $size * 1099511627776; // Gigabytes case 'G': return (int) $size * 1073741824; // Megabytes case 'M': return (int) $size * 1048576; // Kilobytes case 'K': return (int) $size * 1024; default: return (int) $size; } } /** * Convert size string with "shorthand byte" notation to raw byte value for comparisons. * * @since 3.20 * * @param int $bytes * @param int $precision * * @return string size in "shorthand byte" notation */ protected function get_size_in_shorthand( $bytes = 0, $precision = 2 ) { $units = array( ' bytes', 'KB', 'MB', 'GB', 'TB' ); $i = 0; while ( $bytes > 1024 ) { $bytes /= 1024; $i++; } return round( $bytes, $precision ) . $units[ $i ]; } /** * Size comparisons between two values using a variety of calculation methods. * * @since 3.20.2 * * @param string|int|float $a Our value to compare against * @param string|int|float $b Server value being compared * @param string $type Comparison type * * @return bool Whether the second value is equal to or greater than the first */ protected function value_is_at_least( $a, $b, $type = 'size' ) { switch ( $type ) { case 'truthy_falsy': return $this->value_is_falsy( $a ) === $this->value_is_falsy( $b ); case 'version': return (float) $a <= (float) $b; case 'seconds': return (int) $a <= (int) $b; case 'size': default: return $this->get_size_in_bytes( $a ) <= $this->get_size_in_bytes( $b ); } } /** * Check value against a collection of "falsy" values * * @since 3.23 * * @param string|int|float $a Value to compare against * * @return bool Whether the second value is equal to or greater than the first */ protected function value_is_falsy( $a ) { // Accept falsy strings regardless of case (e.g. 'off', 'Off', 'OFF', 'oFf') if ( is_string( $a ) ) { $a = strtolower( $a ); } return in_array( $a, array( false, 'false', 0, '0', 'off' ), true ); } /** * SUPPORT CENTER :: REMOTE ACCESS */ /** * Add Support Center options to the Role Editor screen * * @see ET_Core_SupportCenter::current_user_can() * * @since 3.20 * * @param $all_role_options * * @return array */ public function support_user_add_role_options( $all_role_options ) { // get all the roles that can edit theme options. $applicability_roles = et_core_get_roles_by_capabilities( [ 'edit_theme_options' ] ); $all_role_options['support_center'] = array( 'section_title' => esc_attr__( 'Support Center', 'et-core' ), 'applicability' => $applicability_roles, 'options' => array( 'et_support_center' => array( 'name' => esc_attr__( 'Divi Support Center Page', 'et-core' ), ), 'et_support_center_system' => array( 'name' => esc_attr__( 'System Status', 'et-core' ), ), 'et_support_center_remote_access' => array( 'name' => esc_attr__( 'Remote Access', 'et-core' ), ), 'et_support_center_documentation' => array( 'name' => esc_attr__( 'Divi Documentation & Help', 'et-core' ), ), 'et_support_center_safe_mode' => array( 'name' => esc_attr__( 'Safe Mode', 'et-core' ), ), 'et_support_center_logs' => array( 'name' => esc_attr__( 'Logs', 'et-core' ), ), ), ); return $all_role_options; } /** * Add third party capabilities to Remote Access roles * * @return array Capabilities to add to the Remote Access user roles. */ public function support_user_extra_caps_standard( $extra_capabilities = array() ) { // The Events Calendar (if active on the site) if ( class_exists( 'Tribe__Events__Main' ) ) { $the_events_calendar = array( // Events 'edit_tribe_event' => 1, 'read_tribe_event' => 1, 'delete_tribe_event' => 1, 'delete_tribe_events' => 1, 'edit_tribe_events' => 1, 'edit_others_tribe_events' => 1, 'delete_others_tribe_events' => 1, 'publish_tribe_events' => 1, 'edit_published_tribe_events' => 1, 'delete_published_tribe_events' => 1, 'delete_private_tribe_events' => 1, 'edit_private_tribe_events' => 1, 'read_private_tribe_events' => 1, // Venues 'edit_tribe_venue' => 1, 'read_tribe_venue' => 1, 'delete_tribe_venue' => 1, 'delete_tribe_venues' => 1, 'edit_tribe_venues' => 1, 'edit_others_tribe_venues' => 1, 'delete_others_tribe_venues' => 1, 'publish_tribe_venues' => 1, 'edit_published_tribe_venues' => 1, 'delete_published_tribe_venues' => 1, 'delete_private_tribe_venues' => 1, 'edit_private_tribe_venues' => 1, 'read_private_tribe_venues' => 1, // Organizers 'edit_tribe_organizer' => 1, 'read_tribe_organizer' => 1, 'delete_tribe_organizer' => 1, 'delete_tribe_organizers' => 1, 'edit_tribe_organizers' => 1, 'edit_others_tribe_organizers' => 1, 'delete_others_tribe_organizers' => 1, 'publish_tribe_organizers' => 1, 'edit_published_tribe_organizers' => 1, 'delete_published_tribe_organizers' => 1, 'delete_private_tribe_organizers' => 1, 'edit_private_tribe_organizers' => 1, 'read_private_tribe_organizers' => 1, ); $extra_capabilities = array_merge( $extra_capabilities, $the_events_calendar ); } return $extra_capabilities; } /** * Add third party capabilities to the *Elevated* Remote Access role only * * @return array Capabilities to add to the Elevated Remote Access user role. */ public function support_user_extra_caps_elevated() { $extra_capabilities = array(); return $extra_capabilities; } /** * Create the Divi Support user (if it doesn't already exist) * * @since 3.20 * * @return void|WP_Error */ public function support_user_maybe_create_user() { if ( username_exists( $this->support_user_account_name ) ) { return; } // Define user roles that will be used to control ET Support User permissions $this->support_user_create_roles(); $token = $this->support_user_generate_token(); $password = $this->support_user_generate_password( $token ); if ( is_wp_error( $password ) ) { return $password; } $user_id = wp_insert_user( array( 'user_login' => $this->support_user_account_name, 'user_pass' => $password, 'first_name' => 'Elegant Themes', 'last_name' => 'Support', 'display_name' => 'Elegant Themes Support', 'role' => 'et_support', ) ); if ( is_wp_error( $user_id ) ) { return $user_id; } $account_settings = array( 'date_created' => time(), 'token' => $token, ); update_option( $this->support_user_options_name, $account_settings ); // update options variable $this->support_user_get_options(); $this->support_user_init_cron_delete_account(); } /** * Define both Standard and Elevated roles for the Divi Support user * * @since 3.22 Added filters to extend the list of capabilities for the ET Support User * @since 3.20 */ public function support_user_create_roles() { // Make sure old versions of these roles do not exist $this->support_user_remove_roles(); // Divi Support :: Standard $standard_capabilities = array( 'assign_product_terms' => true, 'delete_pages' => true, 'delete_posts' => true, 'delete_private_pages' => true, 'delete_private_posts' => true, 'delete_private_products' => true, 'delete_product' => true, 'delete_product_terms' => true, 'delete_products' => true, 'delete_published_pages' => true, 'delete_published_posts' => true, 'delete_published_products' => true, 'edit_dashboard' => true, 'edit_files' => true, 'edit_others_pages' => true, 'edit_others_posts' => true, 'edit_others_products' => true, 'edit_pages' => true, 'edit_posts' => true, 'edit_private_pages' => true, 'edit_private_posts' => true, 'edit_private_products' => true, 'edit_product' => true, 'edit_product_terms' => true, 'edit_products' => true, 'edit_published_pages' => true, 'edit_published_posts' => true, 'edit_published_products' => true, 'edit_theme_options' => true, 'list_users' => true, 'manage_categories' => true, 'manage_links' => true, 'manage_options' => true, 'manage_product_terms' => true, 'moderate_comments' => true, 'publish_pages' => true, 'publish_posts' => true, 'publish_products' => true, 'read' => true, 'read_private_pages' => true, 'read_private_posts' => true, 'read_private_products' => true, 'read_product' => true, 'unfiltered_html' => true, 'upload_files' => true, // Divi 'ab_testing' => true, 'add_library' => true, 'disable_module' => true, 'divi_builder_control' => true, 'divi_ai' => true, 'divi_library' => true, 'edit_borders' => true, 'edit_buttons' => true, 'edit_colors' => true, 'edit_configuration' => true, 'edit_content' => true, 'edit_global_library' => true, 'edit_layout' => true, 'export' => true, 'lock_module' => true, 'page_options' => true, 'portability' => true, 'read_dynamic_content_custom_fields' => true, 'save_library' => true, 'use_visual_builder' => true, 'theme_builder' => true, // WooCommerce Capabilities 'manage_woocommerce' => true, ); // Divi Support :: Elevated $elevated_capabilities = array_merge( $standard_capabilities, array( 'activate_plugins' => true, 'delete_plugins' => true, 'delete_themes' => true, 'edit_plugins' => true, 'edit_themes' => true, 'install_plugins' => true, 'install_themes' => true, 'switch_themes' => true, 'update_plugins' => true, 'update_themes' => true, ) ); // Filters to allow other code to extend the list of capabilities $additional_standard = apply_filters( 'add_et_support_standard_capabilities', array() ); $additional_elevated = apply_filters( 'add_et_support_elevated_capabilities', array() ); // Apply filter capabilities to our definitions $standard_capabilities = array_merge( $additional_standard, $standard_capabilities ); // Just like Elevated gets all of Standard's capabilities, it also inherits Standard's filter caps $elevated_capabilities = array_merge( $additional_standard, $additional_elevated, $elevated_capabilities ); // Create the standard ET Support role add_role( 'et_support', 'ET Support', $standard_capabilities ); $et_support_role = get_role( 'et_support' ); foreach ( $standard_capabilities as $cap ) { $et_support_role->add_cap( $cap ); } // Create the elevated ET Support role add_role( 'et_support_elevated', 'ET Support - Elevated', $elevated_capabilities ); $et_support_elevated_role = get_role( 'et_support_elevated' ); foreach ( $elevated_capabilities as $cap ) { $et_support_elevated_role->add_cap( $cap ); } } /** * Remove our Standard and Elevated Support roles * * @since 3.20 */ public function support_user_remove_roles() { // Divi Support :: Standard remove_role( 'et_support' ); // Divi Support :: Elevated remove_role( 'et_support_elevated' ); } /** * Set the ET Support User's role * * @since 3.20 * * @param string $role */ public function support_user_set_role( $role = '' ) { // Get the Divi Support User object $support_user = new WP_User( $this->support_user_account_name ); // Set the new Role switch ( $role ) { case 'et_support': $support_user->set_role( 'et_support' ); break; case 'et_support_elevated': $support_user->set_role( 'et_support_elevated' ); break; case '': default: $support_user->set_role( '' ); } } /** * Ensure the `unfiltered_html` capability is added to the ET Support roles in Multisite * * @since 3.22 * * @param array $caps An array of capabilities. * @param string $cap The capability being requested. * @param int $user_id The current user's ID. * * @return array Modified array of user capabilities. */ function support_user_map_meta_cap( $caps, $cap, $user_id ) { if ( ! $this->is_support_user( $user_id ) ) { return $caps; } // This user is in an ET Support user role, so add the capability if ( 'unfiltered_html' === $cap ) { $caps = array( 'unfiltered_html' ); } return $caps; } /** * Remove KSES filters on ET Support User's content * * @since 3.22 */ function support_user_kses_remove_filters() { if ( $this->is_support_user() ) { kses_remove_filters(); } } /** * Clear "Delete Account" cron hook * * @since 3.20 * * @return void */ public function support_user_clear_delete_cron() { wp_clear_scheduled_hook( $this->support_user_cron_name ); } /** * Delete the support account if it's expired or the expiration date is not set * * @since 3.20 * * @return void */ public function support_user_cron_maybe_delete_account() { if ( ! username_exists( $this->support_user_account_name ) ) { return; } if ( isset( $this->support_user_options['date_created'] ) ) { $this->support_user_maybe_delete_expired_account(); } else { // if the expiration date isn't set, delete the account anyway $this->support_user_delete_account(); } } /** * Schedule account removal check * * @since 3.20 * * @return void */ public function support_user_init_cron_delete_account() { $this->support_user_clear_delete_cron(); wp_schedule_event( time(), 'hourly', $this->support_user_cron_name ); } /** * Get plugin options * * @since 3.20 * * @return void */ public function support_user_get_options() { $this->support_user_options = get_option( $this->support_user_options_name ); } /** * Generate random token * * @since 3.20 * * @param integer $length Token Length * @param bool $include_symbols Whether to include special characters (or just stick to alphanumeric) * * @return string $token Generated token */ public function support_user_generate_token( $length = 17, $include_symbols = true ) { $alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $symbols = '!@$^*()-=+'; $token = substr( str_shuffle( $include_symbols ? $alphanum . $symbols : $alphanum ), 0, $length ); return $token; } /** * Generate password from token * * @since 3.20 * * @param string $token Token * * @return string|WP_Error Generated password if successful, WP Error object otherwise */ public function support_user_generate_password( $token ) { global $wp_version; $salt = ''; /** @see ET_Core_SupportCenter::maybe_set_site_id() */ $site_id = get_option( 'et_support_site_id' ); if ( empty( $site_id ) ) { return false; } // Site ID must be a string if ( ! is_string( $site_id ) ) { return false; } $et_license = $this->get_et_license(); if ( ! $et_license ) { return false; } $send_to_api = array( 'action' => 'get_salt', 'site_id' => esc_attr( $site_id ), 'username' => esc_attr( $et_license['username'] ), 'api_key' => esc_attr( $et_license['api_key'] ), 'site_url' => esc_url( home_url( '/' ) ), 'login_url' => 'https://www.elegantthemes.com/members-area/admin/token/' . '?url=' . urlencode( wp_login_url() ) . '&token=' . urlencode( $token . '|' . $site_id ), ); $support_user_options = array( 'timeout' => 30, 'body' => $send_to_api, 'user-agent' => 'WordPress/' . $wp_version . '; Support Center/' . ET_CORE_VERSION . '; ' . home_url( '/' ), ); $request = wp_remote_post( 'https://www.elegantthemes.com/api/token.php', $support_user_options ); // Early exit if we don't get a good HTTP response from the API server if ( 200 !== intval( wp_remote_retrieve_response_code( $request ) ) ) { return new WP_Error( 'et_remote_access', esc_html__( 'Elegant Themes API Error: HTTP error in API response', 'et-core' ) ); } // Early exit and pass along WP_Error report if the server response is an error if ( is_wp_error( $request ) ) { return new WP_Error( 'et_remote_access', esc_html__( 'Elegant Themes API Error: WordPress Error in API response', 'et-core' ) ); } // Otherwise the response is good - let's load it and continue $response = unserialize( wp_remote_retrieve_body( $request ) ); // If the API returns an error, we will return and log the accompanying message $response_is_error = array_key_exists( 'error', $response ); $response_has_error_message = array_key_exists( 'message', $response ); if ( $response_is_error && $response_has_error_message ) { return new WP_Error( 'et_remote_access', esc_html__( 'Elegant Themes API Error: ' . $response['message'], 'et-core' ) ); } // If we get an "Incorrect Token" response, delete the generated Site ID from database $response_is_token_error = array_key_exists( 'incorrect_token', $response ); if ( $response_is_token_error && ! empty( $response['incorrect_token'] ) ) { delete_option( 'et_support_site_id' ); return new WP_Error( 'et_remote_access', esc_html__( 'Elegant Themes API Error: Incorrect Token. Please, try again.', 'et-core' ) ); } // If we get a normal-looking response, but it doesn't contain the salt we need if ( empty( $response['salt'] ) ) { return new WP_Error( 'et_remote_access', esc_html__( 'Elegant Themes API Error: The API response was missing required data.', 'et-core' ) ); } // We have the salt; let's clean it and make sure we can use it $salt = sanitize_text_field( $response['salt'] ); if ( empty( $salt ) ) { return new WP_Error( 'et_remote_access', esc_html__( 'Elegant Themes API Error: The API responded, but the response was empty.', 'et-core' ) ); } // Generate the password using the token we were initially passed & the salt from the API $password = hash( 'sha256', $token . $salt ); return $password; } /** * Delete the account if it's expired * * @since 3.20 * * @return void */ public function support_user_maybe_delete_expired_account() { if ( empty( $this->support_user_options['date_created'] ) ) { return; } $expiration_date_unix = strtotime( $this->support_user_expiration_time, $this->support_user_options['date_created'] ); // Delete the user account if the expiration date is in the past if ( time() >= $expiration_date_unix ) { $this->support_user_delete_account(); } return; } /** * Delete support account and the plugin options ( token, expiration date ) * * @since 3.20 * * @return string | WP_Error Confirmation message on success, WP_Error on failure */ public function support_user_delete_account() { if ( defined( 'DOING_CRON' ) ) { require_once( ABSPATH . 'wp-admin/includes/user.php' ); } if ( ! username_exists( $this->support_user_account_name ) ) { return new WP_Error( 'get_user_data', esc_html__( 'Support account doesn\'t exist.', 'et-core' ) ); } $support_account_data = get_user_by( 'login', $this->support_user_account_name ); if ( $support_account_data ) { $support_account_id = $support_account_data->ID; if ( ( is_multisite() && ! wpmu_delete_user( $support_account_id ) ) || ( ! is_multisite() && ! wp_delete_user( $support_account_id ) ) ) { return new WP_Error( 'delete_user', esc_html__( 'Support account hasn\'t been removed. Try to regenerate token again.', 'et-core' ) ); } delete_option( $this->support_user_options_name ); } else { return new WP_Error( 'get_user_data', esc_html__( 'Cannot get the support account data. Try to regenerate token again.', 'et-core' ) ); } $this->support_user_remove_roles(); $this->support_user_remove_site_id(); $this->support_user_clear_delete_cron(); // update options variable $this->support_user_get_options(); new WP_Error( 'get_user_data', esc_html__( 'Token has been deleted successfully.', 'et-core' ) ); return esc_html__( 'Token has been deleted successfully. ', 'et-core' ); } /** * Maybe delete support account and the plugin options when switching themes * * If a theme change is one of: * - [Divi/Extra] > [Divi/Extra] child theme * - [Divi/Extra] child theme > [Divi/Extra] child theme * - [Divi/Extra] child theme > [Divi/Extra] * ...then we won't change the state of the Remote Access toggle. * * @since 3.23 * * @return string | WP_Error Confirmation message on success, WP_Error on failure */ public function maybe_deactivate_on_theme_switch() { // Don't do anything if the user isn't logged in if ( ! is_user_logged_in() ) { return; } // Don't do anything if the parent theme's name matches the parent of this Support Center instance if ( get_option( 'template' ) === $this->parent_nicename ) { return; } // Leaving Divi/Extra environment; deactivate Support Center $this->support_user_delete_account(); $this->unlist_support_center(); $this->support_center_capabilities_teardown(); } /** * Is this user the ET Support User? * * @since 3.22 * * @param int|null $user_id Pass a User ID to check. We'll get the current user's ID otherwise. * * @return bool Returns whether this user is the ET Support User. */ function is_support_user( $user_id = null ) { $user_id = $user_id ? (int) $user_id : get_current_user_id(); if ( ! $user_id ) { return false; } $user = get_userdata( $user_id ); if ( ! is_object( $user ) || ! property_exists( $user, 'roles' ) ) { return false; } // Gather this user's associated role(s). $user_roles = (array) $user->roles; $user_is_support = false; // First, check the username. if ( ! $this->support_user_account_name === $user->user_login ) { return $user_is_support; } // Determine whether this user has the ET Support User role. if ( in_array( 'et_support', $user_roles, true ) ) { $user_is_support = true; } if ( in_array( 'et_support_elevated', $user_roles, true ) ) { $user_is_support = true; } return $user_is_support; } /** * Delete support account and the plugin options ( token, expiration date ) * * @since 3.20 * * @return void */ public function unlist_support_center() { delete_option( 'et_support_center_installed' ); } /** * */ public function support_user_remove_site_id() { $site_id = get_option( 'et_support_site_id' ); if ( empty( $site_id ) ) { return; } // Site ID must be a string if ( ! is_string( $site_id ) ) { return; } $et_license = $this->get_et_license(); if ( ! $et_license ) { return; } $send_to_api = array( 'action' => 'remove_site_id', 'site_id' => esc_attr( $site_id ), 'username' => esc_attr( $et_license['username'] ), 'api_key' => esc_attr( $et_license['api_key'] ), 'site_url' => esc_url( home_url( '/' ) ), ); $settings = array( 'timeout' => 30, 'body' => $send_to_api, ); $request = wp_remote_post( 'https://www.elegantthemes.com/api/token.php', $settings ); } function support_user_update_via_ajax() { // Verify nonce et_core_security_check( 'manage_options', 'support_center', 'nonce' ); // Get POST data $support_update = sanitize_text_field( $_POST['support_update'] ); $response = array(); // Update option(s) if ( 'activate' === $support_update ) { $maybe_create_user = $this->support_user_maybe_create_user(); // Only activate if we have a User ID and Password if ( ! is_wp_error( $maybe_create_user ) ) { $this->support_user_set_role( 'et_support' ); $account_settings = get_option( $this->support_user_options_name ); $site_id = get_option( 'et_support_site_id' ); $response['expiry'] = strtotime( date( 'Y-m-d H:i:s ', $this->support_user_options['date_created'] ) . $this->support_user_expiration_time ); $response['token'] = ''; if ( ! empty( $site_id ) && is_string( $site_id ) ) { $account_setting_token = isset( $account_settings['token'] ) ? $account_settings['token'] : ''; $response['token'] = $account_setting_token . '|' . $site_id; } $response['message'] = esc_html__( 'ET Support User role has been activated.', 'et-core' ); } else { et_error( $maybe_create_user->get_error_message() ); $response['error'] = $maybe_create_user->get_error_message(); } } if ( 'elevate' === $support_update ) { $this->support_user_set_role( 'et_support_elevated' ); $response['message'] = esc_html__( 'ET Support User role has been elevated.', 'et-core' ); } if ( 'deactivate' === $support_update ) { $this->support_user_set_role( '' ); $this->support_user_delete_account(); $this->support_user_clear_delete_cron(); $response['message'] = esc_html__( 'ET Support User role has been deactivated.', 'et-core' ); } // `echo` data to return if ( isset( $response ) ) { echo json_encode( $response ); } // `die` when we're done wp_die(); } /** * SUPPORT CENTER :: SAFE MODE */ /** * ET Product Allowlist * * @since 3.28 * * @param string $product Potential ET product name that we want to confirm is on the list. * * @return string|false If the product is on our list, we return the "nice name" we have for it. Otherwise, we return FALSE. */ protected function is_allowlisted_product( $product = '' ) { switch ( $product ) { case 'divi_builder_plugin': case 'divi_theme': case 'extra_theme': case 'monarch_plugin': case 'bloom_plugin': case 'divi_dash_plugin': return $this->get_parent_nicename( $product ); break; default: return false; } } /** * Safe Mode: Set session cookie to temporarily disable Plugins * * @since 3.20 * * @return void */ function safe_mode_update_via_ajax() { et_core_security_check( 'manage_options', 'support_center', 'nonce' ); $response = array(); // Get POST data $support_update = sanitize_text_field( $_POST['support_update'] ); // Update option(s) if ( 'activate' === $support_update ) { // Check the ET product that is activating Safe Mode $safe_mode_activator = sanitize_key( $_POST['product'] ); // Confirm that this is a allowlisted product $allowlisted_product = $this->is_allowlisted_product( $safe_mode_activator ); if ( ! $allowlisted_product ) { // Send a failure code and exit the function header( "HTTP/1.0 403 Forbidden" ); print 'Bad or malformed ET product name.'; wp_die(); } $this->toggle_safe_mode( true, $safe_mode_activator ); $response['message'] = esc_html__( 'ET Safe Mode has been activated.', 'et-core' ); } if ( 'deactivate' === $support_update ) { $this->toggle_safe_mode( false ); $response['message'] = esc_html__( 'ET Safe Mode has been deactivated.', 'et-core' ); } $this->set_safe_mode_cookie(); // `echo` data to return if ( isset( $response ) ) { echo json_encode( $response ); } // `die` when we're done wp_die(); } /** * Toggle Safe Mode * * @since 3.20 * * @param bool $activate TRUE if enabling Safe Mode, FALSE if disabling Safe mode. * @param string $product Name of ET product that is activating Safe Mode (@see ET_Core_SupportCenter::get_parent_nicename()). */ public function toggle_safe_mode( $activate = true, $product = '' ) { $activate = (bool) $activate; $user_id = get_current_user_id(); $allowlisted_product = $this->is_allowlisted_product( $product ); // Only proceed with an activation request if it comes from a allowlisted product if ( $activate && ! $allowlisted_product ) { return; } update_user_meta( $user_id, '_et_support_center_safe_mode', $activate ? 'on' : 'off' ); update_user_meta( $user_id, '_et_support_center_safe_mode_product', $activate ? sanitize_text_field( $allowlisted_product ) : '' ); $activate ? $this->maybe_add_mu_autoloader() : $this->maybe_remove_mu_autoloader(); /** * Fires when safe mode is toggled on or off. * * @since 3.25.4 * * @param bool $state True if toggled on, false if toggled off. */ do_action( 'et_support_center_toggle_safe_mode', $activate ); } /** * Set Safe Mode Cookie * * @since 3.20 * * @return void */ function set_safe_mode_cookie() { if ( et_core_is_safe_mode_active() ) { // This random string ensures old cookies aren't used to view the site in Safe Mode $passport = md5( rand() ); update_option( 'et-support-center-safe-mode-verify', $passport ); setcookie( 'et-support-center-safe-mode', $passport, time() + DAY_IN_SECONDS, SITECOOKIEPATH, false, is_ssl() ); } else { // Force-expire the cookie setcookie( 'et-support-center-safe-mode', '', 1, SITECOOKIEPATH, false, is_ssl() ); } } /** * Render modal that intercepts plugin activation/deactivation * * @since 3.20 * * @return void */ public function render_safe_mode_block_restricted() { if ( ! et_core_is_safe_mode_active() ) { return; } // Get the name of the ET product that activated Safe Mode $safe_mode_activator = get_user_meta( get_current_user_id(), '_et_support_center_safe_mode_product', true ); $verified_activator = $this->is_allowlisted_product( $safe_mode_activator ); ?> <script type="text/template" id="et-ajax-safe-mode-template"> <div class="et-core-modal-overlay et-core-form et-core-safe-mode-block-modal"> <div class="et-core-modal"> <div class="et-core-modal-header"> <h3 class="et-core-modal-title"> <?php print esc_html__( 'Safe Mode', 'et-core' ); ?> </h3> <a href="#" class="et-core-modal-close" data-et-core-modal="close"></a> </div> <div id="et-core-safe-mode-block-modal-content"> <div class="et-core-modal-content"> <p><?php print esc_html__( 'Safe Mode is enabled and the current action cannot be performed.', 'et-core' ); ?></p> </div> <a class="et-core-modal-action" href="<?php echo admin_url( null, 'admin.php?page=et_support_center#et_card_safe_mode' ); ?>"> <?php print esc_html__( sprintf( 'Turn Off %1$s Safe Mode', $verified_activator ), 'et-core' ); ?> </a> </div> </div> </div> </script> <?php } /** * Disable Custom CSS (if Safe Mode is active) * * @since 3.20 */ function maybe_disable_custom_css() { // Don't do anything if the user isn't logged in if ( ! is_user_logged_in() ) { return; } if ( et_core_is_safe_mode_active() ) { // Remove "Additional CSS" from WP Head action hook remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); } } /** * Add Safe Mode Indicator (if Safe Mode is active) * * @since 3.20 */ function maybe_add_safe_mode_indicator() { // Don't do anything if the user isn't logged in if ( ! is_user_logged_in() ) { return; } // Don't display when Visual Builder is active if ( et_core_is_fb_enabled() ) { return; } if ( et_core_is_safe_mode_active() ) { // Get the name of the ET product that activated Safe Mode $safe_mode_activator = get_user_meta( get_current_user_id(), '_et_support_center_safe_mode_product', true ); $verified_activator = $this->is_allowlisted_product( $safe_mode_activator ); print sprintf( '<a class="%1$s" href="%2$s">%3$s</a>', 'et-safe-mode-indicator', esc_url( get_admin_url( null, 'admin.php?page=et_support_center#et_card_safe_mode' ) ), esc_html__( sprintf( 'Turn Off %1$s Safe Mode', $verified_activator ), 'et-core' ) ); print sprintf( '<div id="%1$s"><img src="%2$s" alt="%3$s" id="%3$s"/></div>', 'et-ajax-saving', esc_url( $this->local_path . 'admin/images/ajax-loader.gif' ), 'loading' ); } } /** * Prints the admin page for Support Center * * @since 3.20 */ public function add_support_center() { $is_current_user_et_support = 0; if ( in_array( 'et_support', wp_get_current_user()->roles ) ) { $is_current_user_et_support = 1; } if ( in_array( 'et_support_elevated', wp_get_current_user()->roles ) ) { $is_current_user_et_support = 2; } // Conditionally Display Divi Hosting Card $this->maybe_display_divi_hosting_card(); ?> <div id="et_support_center" class="wrap et-divi-admin-page--wrapper" data-et-zone="wp-admin" data-et-page="wp-admin-support-center"> <h1><?php esc_html_e( sprintf( '%1$s Help & Support Center', $this->parent_nicename ), 'et-core' ); ?></h1> <div id="epanel"> <div id="epanel-content"> <?php /** * Run code before any of the Support Center cards have been output * * @since 3.20 */ do_action( 'et_support_center_above_cards' ); // Build Card :: System Status if ( $this->current_user_can( 'et_support_center_system' ) ) { $card_title = esc_html__( 'System Status', 'et-core' ); $card_content = sprintf( '<div class="et-system-status summary">%1$s</div>' . '<textarea id="et_system_status_plain">%2$s</textarea>' . '<div class="et_card_cta">%3$s %4$s %5$s</div>', et_core_intentionally_unescaped( $this->system_diagnostics_generate_report( true, 'div' ), 'html' ), et_core_intentionally_unescaped( $this->system_diagnostics_generate_report( true, 'plain' ), 'html' ), sprintf( '<a class="full_report_show">%1$s</a>', esc_html__( 'Show Full Report', 'et-core' ) ), sprintf( '<a class="full_report_hide">%1$s</a>', esc_html__( 'Hide Full Report', 'et-core' ) ), sprintf( '<a class="full_report_copy">%1$s</a>', esc_html__( 'Copy Full Report', 'et-core' ) ) ); print $this->add_support_center_card( array( 'title' => $card_title, 'content' => $card_content, 'additional_classes' => array( 'et_system_status', 'summary', ), ) ); } /** * Run code after the 1st Support Center card has been output * * @since 3.20 */ do_action( 'et_support_center_below_position_1' ); // Build Card :: Remote Access if ( $this->current_user_can( 'et_support_center_remote_access' ) && ( 0 === $is_current_user_et_support ) ) { $card_title = esc_html__( 'Elegant Themes Support', 'et-core' ); $card_content = __( '<p>Enabling <strong>Remote Access</strong> will give the Elegant Themes support team limited access to your WordPress Dashboard. If requested, you can also enable full admin privileges. Remote Access should only be turned on if requested by the Elegant Themes support team. Remote Access is automatically disabled after 4 days.</p>', 'et-core' ); $support_account = $this->get_et_support_user(); $is_et_support_user_active = 0; $has_et_license = $this->get_et_license(); if ( ! $has_et_license ) { $card_content .= sprintf( '<div class="et-support-user"><h4>%1$s</h4><p>%2$s</p></div>', esc_html__( 'Remote Access', 'et-core' ), __( 'Remote Access cannot be enabled because you do not have a valid API Key or your Elegant Themes subscription has expired. You can find your API Key by <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">logging in</a> to your Elegant Themes account. It should then be added to your <a href="https://www.elegantthemes.com/documentation/divi/update-divi/" target=_blank">Options Panel</a>.', 'et-core' ) ); } else { if ( is_object( $support_account ) && property_exists( $support_account, 'roles' ) ) { if ( in_array( 'et_support', $support_account->roles ) ) { $is_et_support_user_active = 1; } if ( in_array( 'et_support_elevated', $support_account->roles ) ) { $is_et_support_user_active = 2; } } $support_user_active_state = ( intval( $is_et_support_user_active ) > 0 ) ? ' et_pb_on_state' : ' et_pb_off_state'; $expiry = ''; if ( ! empty( $this->support_user_options['date_created'] ) ) { // Calculate the 'Created Date' plus the 'Time To Expire' $date_created = date( 'Y-m-d H:i:s ', $this->support_user_options['date_created'] ); $expiry = strtotime( $date_created . $this->support_user_expiration_time ); } // Toggle Support User activation $card_content .= sprintf( '<div class="et-support-user"><h4>%1$s</h4>' . '<div class="et_support_user_toggle">' . '<div class="%7$s_wrapper"><div class="%7$s %2$s">' . '<span class="%8$s et_pb_on_value">%3$s</span>' . '<span class="et_pb_button_slider"></span>' . '<span class="%8$s et_pb_off_value">%4$s</span>' . '</div></div>' . '<span class="et-support-user-expiry" data-expiry="%5$s">%6$s' . '<span class="support-user-time-to-expiry"></span>' . '</span>' . '<span class="et-remote-access-error"></span>' . '</div>' . '</div>', esc_html__( 'Remote Access', 'et-core' ), esc_attr( $support_user_active_state ), esc_html__( 'Enabled', 'et-core' ), esc_html__( 'Disabled', 'et-core' ), esc_attr( $expiry ), esc_html__( 'Remote Access will be automatically disabled in: ', 'et-core' ), 'et_pb_yes_no_button', 'et_pb_value_text' ); // Toggle Support User role elevation (only visible if Support User is active) $extra_css = ( intval( $is_et_support_user_active ) > 0 ) ? 'style="display:block;"' : ''; $support_user_elevated_state = ( intval( $is_et_support_user_active ) > 1 ) ? ' et_pb_on_state' : ' et_pb_off_state'; $card_content .= sprintf( '<div class="et-support-user-elevated" %5$s><h4>%1$s</h4>' . '<div class="et_support_user_elevated_toggle">' . '<div class="%6$s_wrapper"><div class="%6$s %2$s">' . '<span class="%7$s et_pb_on_value">%3$s</span>' . '<span class="et_pb_button_slider"></span>' . '<span class="%7$s et_pb_off_value">%4$s</span>' . '</div></div>' . '</div>' . '</div>', esc_html__( 'Activate Full Admin Privileges', 'et-core' ), esc_attr( $support_user_elevated_state ), esc_html__( 'Enabled', 'et-core' ), esc_html__( 'Disabled', 'et-core' ), et_core_intentionally_unescaped( $extra_css, 'html' ), 'et_pb_yes_no_button', 'et_pb_value_text' ); } // Add a "Copy Support Token" CTA if Remote Access is active $site_id = get_option( 'et_support_site_id' ); $support_token_cta = ''; if ( intval( $is_et_support_user_active ) > 0 && ! empty( $site_id ) && is_string( $site_id ) ) { $account_settings = get_option( $this->support_user_options_name ); $account_setting_token = isset( $account_settings['token'] ) ? $account_settings['token'] : ''; $support_token_cta = '<a class="copy_support_token" data-token="' . esc_attr( $account_setting_token . '|' . $site_id ) . '">' . esc_html__( 'Copy Support Token', 'et-core' ) . '</a>'; } $vip_support_content = '<div class="et_vip_support">' . '<div class="et_vip_support__left">' . '<a target="_blank" href="https://www.elegantthemes.com/vip/?utm_source=Divi+VIP&utm_medium=Support+Center&utm_campaign=Native">' . '<img src="' . esc_url( ET_CORE_URL ) . 'admin/images/blurb-vip.jpg" alt="Divi VIP Support" />' . '</a>' . '</div>' . '<div class="et_vip_support__right">' . '<h2>' . esc_html__( 'Get More With Divi VIP', 'et-core' ) . '</h2>' . '<h2>' . esc_html__( 'The Best Support, Even Faster.', 'et-core' ) . '</h2>' . '<p>' . esc_html__( 'We want to provide exactly the level of support any of our customers need to be successful. With Divi VIP, you get faster support (Under 30 minutes response times around the clock). Keep your clients happy by letting us solve their problems faster.', 'et-core' ) . '</p>' . '<a target="_blank" href="https://www.elegantthemes.com/vip/?utm_source=Divi+VIP&utm_medium=Support+Center&utm_campaign=Native">' . esc_html__( 'Get Divi VIP Today!', 'et-core' ) . '</a>' . '</div>' . '</div>'; $card_content .= '<div class="et_card_cta">' . '<a target="_blank" href="https://www.elegantthemes.com/members-area/help/">' . esc_html__( 'Chat With Support', 'et-core' ) . '</a>' . $support_token_cta . $vip_support_content . '</div>'; print $this->add_support_center_card( array( 'title' => $card_title, 'content' => $card_content, 'additional_classes' => array( 'et_remote_access', 'et-epanel-box', ), ) ); } /** * Run code after the 2nd Support Center card has been output * * @since 3.20 */ do_action( 'et_support_center_below_position_2' ); // Build Card :: Divi Documentation & Help if ( $this->current_user_can( 'et_support_center_documentation' ) ) { switch ( $this->parent ) { case 'extra_theme': $documentation_url = 'https://www.elegantthemes.com/documentation/extra/'; break; case 'divi_theme': $documentation_url = 'https://www.elegantthemes.com/documentation/divi/'; break; case 'divi_builder_plugin': $documentation_url = 'https://www.elegantthemes.com/documentation/divi-builder/'; break; case 'monarch_plugin': $documentation_url = 'https://www.elegantthemes.com/documentation/monarch/'; break; case 'bloom_plugin': $documentation_url = 'https://www.elegantthemes.com/documentation/bloom/'; break; default: $documentation_url = 'https://www.elegantthemes.com/documentation/'; } $card_title = esc_html__( sprintf( '%1$s Documentation & Help', $this->parent_nicename ), 'et-core' ); $card_content = $this->get_documentation_video_player(); $card_content .= $this->get_documentation_articles_list(); $card_content .= '<div class="et_card_cta">' . '<a href="' . $documentation_url . '" class="launch_documentation" target="_blank">' . esc_html__( sprintf( 'View Full %1$s Documentation', $this->parent_nicename ), 'et-core' ) . '</a>' . '</div>'; print $this->add_support_center_card( array( 'title' => $card_title, 'content' => $card_content, 'additional_classes' => array( 'et_documentation_help', 'et-epanel-box', ), ) ); } /** * Run code after the 3rd Support Center card has been output * * @since 3.20 */ do_action( 'et_support_center_below_position_3' ); // Build Card :: Safe Mode if ( $this->current_user_can( 'et_support_center_safe_mode' ) ) { $card_title = esc_html__( 'Safe Mode', 'et-core' ); $card_content = __( '<p>Enabling <strong>Safe Mode</strong> will temporarily disable features and plugins that may be causing problems with your Elegant Themes product. This includes all Plugins, Child Themes, and Custom Code added to your integration areas. These items are only disabled for your current user session so your visitors will not be disrupted. Enabling Safe Mode makes it easy to figure out what is causing problems on your website by identifying or eliminating third party plugins and code as potential causes.</p>', 'et-core' ); $error_message = ''; $safe_mode_active = ( et_core_is_safe_mode_active() ) ? ' et_pb_on_state' : ' et_pb_off_state'; $plugins_list = array(); $plugins_output = ''; $has_mu_plugins_dir = wp_mkdir_p( WPMU_PLUGIN_DIR ) && wp_is_writable( WPMU_PLUGIN_DIR ); $can_create_mu_plugins_dir = wp_is_writable( WP_CONTENT_DIR ) && ! wp_mkdir_p( WPMU_PLUGIN_DIR ); if ( $has_mu_plugins_dir || $can_create_mu_plugins_dir ) { // Gather list of plugins that will be temporarily deactivated in Safe Mode $all_plugins = get_plugins(); $active_plugins = get_option( 'active_plugins' ); foreach ( $active_plugins as $plugin ) { // Verify this 'active' plugin actually exists in the plugins directory if ( ! in_array( $plugin, array_keys( $all_plugins ) ) ) { continue; } // If it's not in our allowlist, add it to the list of plugins we'll disable if ( ! in_array( $plugin, $this->safe_mode_plugins_allowlist ) ) { $plugins_list[] = '<li>' . esc_html( $all_plugins[ $plugin ]['Name'] ) . '</li>'; } } } else { $error_message = et_get_safe_localization( sprintf( __( '<p class="et-safe-mode-error">Plugins cannot be disabled because your <code>wp-content</code> directory has inconsistent file permissions. <a href="%1$s" target="_blank">Click here</a> for more information.</p>', 'et-core' ), 'https://wordpress.org/support/article/changing-file-permissions/' ) ); } if ( count( $plugins_list ) > 0 ) { $plugins_output = sprintf( '<p>%1$s</p><ul>%2$s</ul>', esc_html__( 'The following plugins will be temporarily disabled for you only:', 'et-core' ), et_core_intentionally_unescaped( implode( ' ', $plugins_list ), 'html' ) ); } // Toggle Safe Mode activation $card_content .= sprintf( '<div id="et_card_safe_mode" class="et-safe-mode" data-et-product="%8$s">' . '<div class="et_safe_mode_toggle">' . '<div class="%5$s_wrapper"><div class="%5$s %1$s">' . '<span class="%6$s et_pb_on_value">%2$s</span>' . '<span class="et_pb_button_slider"></span>' . '<span class="%6$s et_pb_off_value">%3$s</span>' . '</div></div>' . '%4$s' . '%7$s' . '</div>' . '</div>', esc_attr( $safe_mode_active ), esc_html__( 'Enabled', 'et-core' ), esc_html__( 'Disabled', 'et-core' ), $plugins_output, 'et_pb_yes_no_button', 'et_pb_value_text', $error_message, esc_attr( $this->parent ) ); print $this->add_support_center_card( array( 'title' => $card_title, 'content' => $card_content, 'additional_classes' => array( 'et_safe_mode', 'et-epanel-box', ), ) ); } /** * Run code after the 4th Support Center card has been output * * @since 3.20 */ do_action( 'et_support_center_below_position_4' ); // Build Card :: Logs if ( $this->current_user_can( 'et_support_center_logs' ) ) { $debug_log_lines = apply_filters( 'et_debug_log_lines', 200 ); $wp_debug_log = $this->get_wp_debug_log( $debug_log_lines ); $card_title = esc_html__( 'Logs', 'et-core' ); $card_content = '<p>If you have <a href="https://codex.wordpress.org/Debugging_in_WordPress" target=_blank" >WP_DEBUG_LOG</a> enabled, WordPress related errors will be archived in a log file. For your convenience, we have aggregated the contents of this log file so that you and the Elegant Themes support team can view it easily. The file cannot be edited here.</p>'; if ( isset( $wp_debug_log['error'] ) ) { $card_content .= '<div class="et_system_status_log_preview">' . '<textarea>' . $wp_debug_log['error'] . '</textarea>' . '</div>'; } else { $card_content .= '<div class="et_system_status_log_preview">' . '<textarea id="et_logs_display">' . $wp_debug_log['entries'] . '</textarea>' . '<textarea id="et_logs_recent">' . $wp_debug_log['entries'] . '</textarea>' . '</div>' . '<div class="et_card_cta">' . '<a href="' . content_url( 'debug.log' ) . '" class="download_debug_log" download>' . esc_html__( 'Download Full Debug Log', 'et-core' ) . ' (' . $wp_debug_log['size'] . ')' . '</a>' . '<a class="copy_debug_log">' . esc_html__( 'Copy Recent Log Entries', 'et-core' ) . '</a>' . '</div>'; } print $this->add_support_center_card( array( 'title' => $card_title, 'content' => $card_content, 'additional_classes' => array( 'et_system_logs', 'et-epanel-box', ), ) ); } /** * Run code after all of the Support Center cards have been output * * @since 3.20 */ do_action( 'et_support_center_below_cards' ); ?> </div> </div> </div> <div id="et-ajax-saving"> <img src="<?php echo esc_url( $this->local_path . 'admin/images/ajax-loader.gif' ); ?>" alt="loading" id="loading" /> </div> <?php } /** * SUPPORT CENTER :: DIVI HOSTING CARD */ /** * Conditionally display Divi Hosting Card in the Support Center * * @since 4.4.7 */ public function maybe_display_divi_hosting_card() { // Sanity Check: Exit early if the user does not have permission if ( ! $this->current_user_can( 'et_support_center_system' ) ) { return; } // Exit if Admin dismissed the Divi Hosting Card if ( get_option( 'et_hosting_card_dismissed', false ) ) { return; } // Show the Divi Hosting Card add_action( 'et_support_center_below_position_1', array( $this, 'print_divi_hosting_card' ) ); } /** * Prepare Settings for ET API request * Returns false when ET username/api_key is not found, and ET subscription is not active * * @since 4.4.7 * @param string $action * * @return bool|array */ protected function get_et_api_request_settings( $action ) { $et_account = et_core_get_et_account(); $et_username = et_()->array_get( $et_account, 'et_username', '' ); $et_api_key = et_()->array_get( $et_account, 'et_api_key', '' ); // Only when ET Username and ET API Key is found if ( '' !== $et_username && '' !== $et_api_key ) { global $wp_version; // Prepare settings for API request return array( 'timeout' => 30, 'body' => array( 'action' => $action, 'username' => $et_username, 'api_key' => $et_api_key, ), 'user-agent' => 'WordPress/' . $wp_version . '; Support Center/' . ET_CORE_VERSION . '; ' . home_url( '/' ), ); } return false; } /** * Check ET API whether ET User has disabled Divi Hosting Card * * @since 4.4.7 * * @return bool */ protected function maybe_api_has_hosting_card_disabled() { // Get API settings $api_settings = $this->get_et_api_request_settings( 'check_hosting_card_status' ); // Check API only when ET Username, ET API Key is found and Account is active if ( is_array( $api_settings ) ) { $request = wp_remote_post( 'https://www.elegantthemes.com/api/api.php', $api_settings ); $request_response_code = wp_remote_retrieve_response_code( $request ); // Do not show the Hosting Card when API Request, or, API Response has any error if ( is_wp_error( $request ) || 200 !== $request_response_code ) { return true; } $response_body = wp_remote_retrieve_body( $request ); $response = (array) json_decode( $response_body ); // Check whether the User has disabled the card if ( et_()->array_get( $response, 'success' ) && et_()->array_get( $response, 'status' ) === 'disabled' ) { // Mark it dismissed, so it won't be displayed anymore on this website update_option( 'et_hosting_card_dismissed', true ); // Do not show the Hosting Card return true; } } // Show the Hosting Card return false; } /** * Return Data for Divi Hosting Card * * @since 4.4.7 * * @return array */ protected function get_divi_hosting_features() { return array( 'title' => esc_html__( 'Get Recommended Divi Hosting', 'et-core' ), 'summary' => esc_html__( 'Upgrade your hosting to the most reliable, Divi-compatible hosting. Enjoy perfectly configured hosting environments pre-installed with the tools you need to be successful with Divi.', 'et-core' ), 'url' => 'https://www.elegantthemes.com/hosting/', 'learn_more' => esc_html__( 'Learn About Divi Hosting', 'et-core' ), 'dismiss_tooltip' => esc_html__( 'Remove This Recommendation On All Of Your Websites And Your Client\'s Websites Forever', 'et-core' ), 'features' => array( 'server' => array( 'title' => esc_html__( 'Divi-Optimized Servers', 'et-core' ), 'tooltip' => esc_html__( "We worked with our parters to make sure that their hosting solutions meet all of Divi's requirements out of the box. No hosting headaches on Divi Hosting.", 'et-core' ), ), 'speed' => array( 'title' => esc_html__( 'Blazing Fast Speed', 'et-core' ), 'tooltip' => esc_html__( 'Divi Hosting is powered by fast networks, modern hosting infrastructures and the latest server software. Plus you will enjoy automatic caching and a free CDN.', 'et-core' ), ), 'security' => array( 'title' => esc_html__( 'A Focus On Security', 'et-core' ), 'tooltip' => esc_html__( 'All of our hosting partners are dedicated to security. That means up-to-date server software and secure hosting practices.', 'et-core' ), ), 'backups' => array( 'title' => esc_html__( 'Automatic Backups', 'et-core' ), 'tooltip' => esc_html__( 'Every website needs backups! Each of our hosting partners provide automatic daily backups. If disaster strikes, these hosting companies have your back.', 'et-core' ), ), 'migrate' => array( 'title' => esc_html__( 'Easy Site Migration', 'et-core' ), 'tooltip' => esc_html__( "Already have a Divi website hosted somewhere else? All of our hosting partners provide migration tools or professional assisted migration. It's easy to switch to Divi Hosting!", 'et-core' ), ), 'staging' => array( 'title' => esc_html__( 'Easy Staging Sites', 'et-core' ), 'tooltip' => esc_html__( 'Automatic staging sites make it easy to develop new designs for your clients without disrupting visitors. Finish your work and push it live all at once.', 'et-core' ), ), ), ); } /** * Build and display Divi Hosting Card * * @since 4.4.7 */ public function print_divi_hosting_card() { // Gather System status data $report = $this->system_diagnostics_generate_report( false ); $result = array(); // Prepare the report data to check against when to show the Divi Hosting Card foreach ( $report as $status ) { $result[] = et_()->array_get( $status, 'pass_fail' ); } // Exit if any system status item is not in a warning state (red dot indicator) if ( ! in_array( 'fail', array_values( $result ), true ) ) { return; } // Exit if ET User has disabled the Divi Hosting card if ( $this->maybe_api_has_hosting_card_disabled() ) { return; } // JS dependency for Tooltips wp_enqueue_script( 'popper', $this->local_path . 'admin/js/popper.min.js', array( 'jquery' ), ET_CORE_VERSION ); wp_enqueue_script( 'tippy', $this->local_path . 'admin/js/tippy.min.js', array( 'jquery', 'popper' ), ET_CORE_VERSION ); $card = $this->get_divi_hosting_features(); $features = ''; // HTML Template for Features of the Divi Hosting Card foreach ( $card['features'] as $name => $feature ) { $features .= sprintf( '<div class="et_hosting_card--feature" data-tippy-content="%1$s">%2$s <h4>%3$s</h4></div>', esc_html( $feature['tooltip'] ), sprintf( '<object type="image/svg+xml" className="fitvidsignore" data="%1$s" width="32" height="32"></object>', esc_url( "{$this->local_path}admin/images/svg/{$name}.svg" ) ), esc_html( $feature['title'] ) ); } // HTML Template for the Divi Hosting Card $card_content = sprintf( '<p class="et_card_summary">%1$s</p> <div class="et_card_content et_hosting_card--features">%2$s</div> <div class="et_card_cta et_hosting_card--cta">%3$s</div>', esc_html( $card['summary'] ), et_core_esc_previously( $features ), sprintf( '<a class="et_hosting_card--link" target="_blank" href="%1$s" title="%2$s">%3$s</a>', esc_url( $card['url'] ), esc_attr( $card['learn_more'] ), esc_html( $card['learn_more'] ) ) ); // Display the Divi Hosting Card print $this->add_support_center_card( array( 'title' => $card['title'], 'content' => $card_content, 'additional_classes' => array( 'et_hosting_card', ), 'dismiss_button' => array( 'card_key' => 'et_hosting_card', 'tooltip' => $card['dismiss_tooltip'], 'additional_classes' => array( 'et_hosting_card--dismiss', ), ), ) ); } }
| ver. 1.4 |
Github
|
.
| PHP 8.4.10 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings