| Current Path : /home/flapst5/www/wp-content/plugins/wpmudev-updates/includes/ |
| Current File : /home/flapst5/www/wp-content/plugins/wpmudev-updates/includes/class-wpmudev-dashboard-site.php |
<?php
/**
* Site module.
* Manages all access to the local WordPress site;
* For example: Storing and fetching settings, activating plugins, etc.
*
* @since 4.0.0
* @package WPMUDEV_Dashboard
*/
/**
* The site-module class.
*/
class WPMUDEV_Dashboard_Site {
/**
* URL to the Dashboard plugin; used to load images, etc.
*
* @var string (URL)
*/
public $plugin_url = '';
/**
* Name of the Dashboard plugin directory (relative to wp-content/plugins)
*
* @var string (Path)
*/
public $plugin_dir = '';
/**
* Full path to the plugin directory
*
* @var string (Path)
*/
public $plugin_path = '';
/**
* The PID of the Upfront root theme.
* Upfront is required for our modern themes. This is used to automatically
* install Upfront when required.
*
* @var int (Project ID)
*/
public $id_upfront = 938297;
/**
* The PID of the Upfront builder plugin.
* Upfront is required for this plugin to function. This is used to automatically
* install Upfront when required.
*
* @var int (Project ID)
*/
public $id_upfront_builder = 1107287;
/**
* The PID of our "133 Theme Pack" package.
* This package needs some special treatment; since it contains many themes
* we need to update the package when only one of those themes changed.
*
* @var int (Project ID)
*/
public $id_farm133_themes = 128;
/**
* This is the highest Project-ID of the legacy themes: If the theme has an
* higher ID it means that it requires Upfront.
*
* @var int (Project ID)
*/
public $id_legacy_themes = 237;
/**
* Flag that is tripped to schedule api refresh at the end of the page load (avoid multiple)
*
* @var bool
*/
protected static $refresh_shutdown_flag = false;
/**
* Memoize the local projects.
*
* @var ?array
*/
protected static $memoized_local_projects = null;
/**
* Memoize the updates.
*
* @var ?array
*/
protected static $memoized_updates = null;
/**
* Memoize the plugin updates.
*
* @var ?array
*/
protected static $memoized_plugin_updates = null;
/**
* Memoize the translation updates.
*
* @var ?array
*/
protected static $memoized_translation_updates = null;
/**
* Memoize the theme updates.
*
* @var ?array
*/
protected static $memoized_theme_updates = null;
/**
* Stores return values of get_project_info()
*
* @var array
*/
protected static $cache_project_info = false;
/**
* The noticeid of the SSO notice
*
* @var int (Task ID's last 4 digits)
*/
protected $sso_notice_id = 2927;
/**
* Set up the Site module. Here we load and initialize the settings.
*
* @since 4.0.0
*
* @param string $main_file Path to the plugins main file.
*
* @internal
*/
public function __construct( $main_file ) {
$this->init_flags();
// Prepare module settings.
$this->plugin_url = trailingslashit( plugins_url( '', $main_file ) );
$this->plugin_dir = dirname( plugin_basename( $main_file ) );
$this->plugin_path = trailingslashit( dirname( $main_file ) );
// Process any actions triggered by the UI (e.g. save data).
add_action( 'current_screen', array( $this, 'process_actions' ) );
// Check for compatibility issues and display a notification if needed.
add_action(
'admin_init',
array( $this, 'compatibility_warnings' )
);
add_action( 'update-core.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
add_action( 'load-plugins.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
add_action( 'load-update.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
add_action( 'load-update-core.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
add_action( 'load-themes.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
// really only used when hacking version num.
add_action( 'load-plugin-editor.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
add_action( 'load-theme-editor.php', array( $this, 'refresh_local_projects_wrapper' ), 99 );
// Refresh after upgrade/install.
add_action(
'upgrader_process_complete',
array( $this, 'after_local_files_changed' ),
10,
999
);
add_action(
'set_site_transient_update_plugins',
array( $this, 'schedule_shutdown_refresh' )
);
add_action(
'set_site_transient_update_themes',
array( $this, 'schedule_shutdown_refresh' )
);
add_filter(
'delete_site_transient_update_themes',
array( $this, 'schedule_shutdown_refresh' )
); // runs when a theme is deleted.
// refresh after plugin/theme is activated/deactivated/deleted.
add_action( 'activated_plugin', array( $this, 'schedule_shutdown_refresh' ) );
add_action( 'deactivated_plugin', array( $this, 'schedule_shutdown_refresh' ) );
add_action( 'deleted_plugin', array( $this, 'schedule_shutdown_refresh' ) );
if ( is_multisite() ) {
add_action( 'update_site_option_allowedthemes', array( $this, 'schedule_shutdown_refresh' ) ); // network enable/disable.
}
if ( is_main_site() ) {
add_action( 'after_switch_theme', array( $this, 'schedule_shutdown_refresh' ) ); // per site activation.
}
add_action( 'shutdown', array( $this, 'shutdown_refresh' ) );
// Add WPMUDEV projects to the WP updates list.
add_filter(
'site_transient_update_plugins',
array( $this, 'filter_plugin_update_count' )
);
add_filter(
'site_transient_update_themes',
array( $this, 'filter_theme_update_count' )
);
// Override the theme/plugin-installation API of WordPress core.
add_filter(
'plugins_api',
array( $this, 'filter_plugin_update_info' ),
101,
3 // Run later to work with bad autoupdate plugins.
);
// Whitelabel-ing plugin(s) pages.
add_action(
'admin_enqueue_scripts',
array( $this, 'whitelabel_plugin_admin_pages' )
);
// Tracking code for analytics module.
add_action( 'wp_footer', array( $this, 'analytics_tracking_code' ) );
// Show friendly error in the login screen, if SSO is disabled or user is not logged in the Dashboard.
add_filter( 'login_message', array( $this, 'show_sso_friendly_error' ) );
/**
* Run custom initialization code for the Site module.
*
* @since 4.0.0
* @var WPMUDEV_Dashboard_Site The dashboards Site module.
*/
do_action( 'wpmudev_dashboard_site_init', $this );
add_filter(
'ajax_query_attachments_args',
array( $this, 'user_can_edit_branding_image' )
);
// Handle plugin update changes.
add_action( 'wpmudev_dashboard_version_upgrade', array( $this, 'upgrade_plugin' ) );
// Setup first time actions.
add_action( 'wpmudev_dashboard_first_activation', array( $this, 'first_time_actions' ) );
}
/*
* *********************************************************************** *
* * INTERNAL HELPER FUNCTIONS
* *********************************************************************** *
*/
/**
* Defines missing const flags with default values.
* This saves us from checking `if ( defined( ... ) )` all the time.
*
* Complete list of all supported Dashboard constants:
*
* - WPMUDEV_APIKEY .. Default: (undefined)
* Define a static API key that cannot be changed via Dashboard.
* If this constant is used then login/logout functions are not
* available in the UI.
*
* - WPMUDEV_LIMIT_TO_USER .. Default: ''
* Additional users that can access the Dashboard (comma separated).
* This constant will override the user-list that can be defined on
* the plugins Settings tab.
*
* - WPMUDEV_DISABLE_REMOTE_ACCESS .. Default: false
* Set to true to disable the plugins external access functions.
*
* - WPMUDEV_MENU_LOCATION .. Default: '3.012'
* Position of the WPMUDEV menu-item in the admin menu.
*
* - WPMUDEV_NO_AUTOACTIVATE .. Default: false
* Default behavior of Install button is install + activate.
* Set to true to only install the plugin, no activation.
* (only for plugins on single-site)
*
* - WPMUDEV_CUSTOM_API_SERVER .. Default: false
* Custom API Server from which to get membership details, etc.
*
* - WPMUDEV_API_SSLVERIFY .. Default: true
* Set to false if you are having ssl errors connecting to our API (insecure).
*
* - WPMUDEV_API_UNCOMPRESSED .. Default: false
* Set to true so API calls request uncompressed response values.
*
* - WPMUDEV_API_DEBUG .. Default: false
* If set to true then all API calls are logged in the WordPress
* logfile. This will only work if WP_DEBUG is enabled as well.
*
* - WPMUDEV_API_DEBUG_ALL .. Default: false
* Adds more details to the output of WPMUDEV_API_DEBUG.
*
* - WPMUDEV_IS_REMOTE .. Default: (undefined)
* This flag is set by the ajax handler after verifying an incoming
* request from the remote dashboard. True means: The request is
* authorized and will be processed.
*
* - WPMUDEV_DISABLE_SSO .. Default: false
* Set to true to disable SSO from the Hub.
*
* To disable all remote calls add this in wp-config (not recommended):
* define( 'WPMUDEV_IS_REMOTE', false );
*
* @since 4.0.0
* @internal
*/
protected function init_flags() {
// Do not initialize: WPMUDEV_APIKEY!
// Do not initialize: WPMUDEV_IS_REMOTE!
$flags = array(
'WPMUDEV_LIMIT_TO_USER' => false,
'WPMUDEV_DISABLE_REMOTE_ACCESS' => false,
'WPMUDEV_MENU_LOCATION' => '3.012',
'WPMUDEV_NO_AUTOACTIVATE' => false,
'WPMUDEV_CUSTOM_API_SERVER' => false,
'WPMUDEV_API_UNCOMPRESSED' => false,
'WPMUDEV_API_SSLVERIFY' => true,
'WPMUDEV_API_DEBUG' => false,
'WPMUDEV_API_DEBUG_ALL' => false,
'WPMUDEV_DISABLE_SSO' => false,
);
foreach ( $flags as $flag => $default_val ) {
if ( ! defined( $flag ) ) {
// constant name is in variable without reducing readability, constant names already predetermined in $flags above.
define( $flag, $default_val ); // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.VariableConstantNameFound
}
}
}
/**
* Upgrade plugin settings after the update.
*
* TODO: this is not working very well on beta / rc versioning. as both variants will be always < (less than) released version, the upgrade method will always triggered.
* we do still want it to be triggered on beta / rc versioning as well, but not in constant manner
*
* @since 4.11
*
* @param string $old_version Old version.
*/
public function upgrade_plugin( $old_version ) {
// If upgrading to 4.11.
if ( version_compare( $old_version, '4.11', '<' ) ) {
// If branding enabled, branding type is custom.
if ( WPMUDEV_Dashboard::$settings->get( 'branding_enabled', 'whitelabel' ) ) {
WPMUDEV_Dashboard::$settings->set( 'branding_type', 'custom', 'whitelabel' );
}
}
// If upgrading to 4.11.2.
if ( version_compare( $old_version, '4.11.2', '<' ) ) {
$enabled = (bool) WPMUDEV_Dashboard::$settings->get( 'autoupdate_dashboard', 'flags' );
// Sync Dash auto update to WP.
WPMUDEV_Dashboard::$upgrader->change_wp_auto_update( $enabled );
}
// If upgrading to 4.11.3.
if ( version_compare( $old_version, '4.11.3', '<' ) ) {
$metrics = (array) WPMUDEV_Dashboard::$settings->get( 'metrics', 'analytics', array() );
if ( ! empty( $metrics ) ) {
// Add new visits metrics as checked.
$metrics[] = 'visits';
// Update metrics.
WPMUDEV_Dashboard::$settings->set( 'metrics', $metrics, 'analytics' );
}
}
// If upgrading to 4.11.4.
if ( version_compare( $old_version, '4.11.4', '<' ) ) {
// Set data settings.
WPMUDEV_Dashboard::$settings->set( 'uninstall_keep_data', true, 'flags' );
WPMUDEV_Dashboard::$settings->set( 'uninstall_preserve_settings', true, 'flags' );
// Refresh profile data to include new user id.
WPMUDEV_Dashboard::$api->refresh_profile();
}
// If upgrading to 4.11.10.
if ( version_compare( $old_version, '4.11.10', '<' ) ) {
// Upgrade settings.
WPMUDEV_Dashboard::$settings->upgrade_41110();
}
// Upgrading to 5.0.0.
if ( version_compare( $old_version, '5.0.0', '<' ) ) {
// Upgrade.
WPMUDEV_Dashboard::$settings->upgrade_500();
}
}
/**
* Perform first activation actions.
*
* On first activation if we are on our hosting,
* enable SSO
*
* @since 4.11.2
*/
public function first_time_actions() {
// On our hosting, if it's first time activation enable few services.
if ( WPMUDEV_Dashboard::$api->is_wpmu_dev_hosting() ) {
$user_id = get_current_user_id();
// If we couldn't find a user.
if ( empty( $user_id ) ) {
// Let's get an admin user now.
$users = WPMUDEV_Dashboard::$site->get_available_users();
if ( ! empty( $users[0] ) ) {
$user_id = $users[0]->ID;
}
}
// we just setup the settings internally, it will be synced later after logged in, and / or hub-sync called
// Set a user for SSO.
WPMUDEV_Dashboard::$settings->set( 'userid', $user_id, 'sso' );
// Enable SSO.
WPMUDEV_Dashboard::$settings->set( 'enabled', true, 'sso' );
}
}
/**
* Process actions when page loads.
*
* @since 4.0.0
*
* @param WP_Screen $current_screen The current_screen object.
*
* @internal
*/
public function process_actions( $current_screen ) {
// Remove the "Changes saved" message when user refreshes the browser window.
if ( empty( $_POST ) ) {
$err = isset( $_GET['failed'] ) ? intval( $_GET['failed'] ) : false;
$ok = isset( $_GET['success'] ) ? intval( $_GET['success'] ) : false;
if ( ( $ok && $ok < time() ) || ( $err && $err < time() ) ) {
$url = esc_url_raw(
remove_query_arg( array( 'success', 'failed', 'wpmudev_msg', 'success-action', 'failed-action' ) )
);
header( 'X-Redirect-From: SITE process_actions top' );
wp_safe_redirect( $url );
exit;
}
}
// Do nothing when the current page is NOT a WPMU DEV menu item.
if ( ! strpos( $current_screen->base, 'page_wpmudev' ) ) {
return;
}
// Do nothing if either action or nonce is missing.
if ( empty( $_REQUEST['action'] ) ) {
return;
}
// dynamic nonce.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) );
if ( empty( $_REQUEST['hash'] ) ) {
return;
}
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['hash'] ) );
// Do nothing if the nonce is invalid.
if ( ! wp_verify_nonce( $nonce, $action ) ) {
return;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
// Do nothing if the user is not allowed to use the Dashboard.
if ( ! $this->allowed_user() ) {
return;
}
$res = $this->process_action( $action );
$redirect = remove_query_arg( array( 'action', 'hash', 'success', 'failed', 'success-action', 'failed-action' ) );
switch ( $res ) {
case 'OK':
$redirect = add_query_arg( 'success', 10 + time(), $redirect );
$redirect = add_query_arg( 'success-action', $action, $redirect );
break;
case 'ERR':
$redirect = add_query_arg( 'failed', 10 + time(), $redirect );
$redirect = add_query_arg( 'failed-action', $action, $redirect );
break;
case 'SILENT':
default:
// Nothing.
break;
}
if ( $redirect ) {
header( 'X-Redirect-From: SITE process_actions bottom' );
wp_safe_redirect( esc_url_raw( $redirect ) );
exit;
}
}
/**
* Internal processing function to execute specific actions upon page load.
* When this function is called we already confirmed that the user is
* permitted to use the dashboard and that a correct nonce was supplied.
*
* @since 4.0.0
*
* @param string $action The action to execute.
*
* @return string Either OK|SOK|ERR|SILENT.
* OK .. Action successful. Display "Saved" message.
* ERR .. Action failed. Display "Failed" message.
* SILENT .. Do not display any message.
* @internal
*/
protected function process_action( $action ) {
do_action( 'wpmudev_dashboard_action_' . $action );
return 'SILENT';
}
/**
* Clear all output buffers and send an JSON reponse to an Ajax request.
*
* @since 4.0.0
*
* @param mixed $data Optional data to return to the Ajax request.
*/
public function send_json_success( $data = null ) {
while ( ob_get_level() ) {
ob_get_clean();
}
wp_send_json_success( $data );
}
/**
* Clear all output buffers and send an JSON reponse to an Ajax request.
*
* @since 4.0.0
*
* @param mixed $data Optional data to return to the Ajax request.
*/
public function send_json_error( $data = null ) {
while ( ob_get_level() ) {
ob_get_clean();
}
wp_send_json_error( $data );
}
/*
* *********************************************************************** *
* * PUBLIC INTERFACE FOR OTHER MODULES
* *********************************************************************** *
*/
/**
* Returns the value of a plugin option.
* The plugins option-prefix is automatically added to the option name.
*
* Use this function instead of direct access via get_site_option()
*
* @since 4.0.0
*
* @param string $name The option name.
* @param bool $prefix Optional. Set to false to not prefix the name.
* @param mixed $default_value Optional. Set value to return if option not found.
*
* @return mixed The option value.
* @deprecated 4.11.10 Use WPMUDEV_Dashboard_Settings:get instead.
*/
public function get_option( $name, $prefix = true, $default_value = false ) {
$mapped = WPMUDEV_Dashboard::$settings->deprecated_get_field_mapping( $name );
return WPMUDEV_Dashboard::$settings->get( $mapped['name'], $mapped['group'], $default_value );
}
/**
* Updates the value of a plugin option.
* The plugins option-prefix is automatically added to the option name.
*
* Use this function instead of direct access via update_site_option()
*
* @since 4.0.0
*
* @param string $name The option name.
* @param mixed $value The new option value.
*
* @return bool
* @deprecated 4.11.10 Use WPMUDEV_Dashboard::$settings->set().
*/
public function set_option( $name, $value ) {
$mapped = WPMUDEV_Dashboard::$settings->deprecated_get_field_mapping( $name );
return WPMUDEV_Dashboard::$settings->set( $mapped['name'], $value, $mapped['group'] );
}
/**
* Add a new plugin setting to the database.
* The plugins option-prefix is automatically added to the option name.
*
* This function will only save the value if the option does not exist yet!
*
* Use this function instead of direct access via add_site_option()
*
* @since 4.0.0
*
* @param string $name The option name.
* @param mixed $value The new option value.
*
* @deprecated 4.11.10 Use WPMUDEV_Dashboard::$settings->add().
*/
public function add_option( $name, $value ) {
$mapped = WPMUDEV_Dashboard::$settings->deprecated_get_field_mapping( $name );
return WPMUDEV_Dashboard::$settings->add( $mapped['name'], $value, $mapped['group'] );
}
/**
* Returns the value of a plugin transient.
* The plugins option-prefix is automatically added to the transient name.
*
* Use this function instead of direct access via get_site_transient()
*
* @since 4.0.0
*
* @param string $name The transient name.
* @param bool $prefix Optional. Set to false to not prefix the name.
*
* @return mixed The transient value.
* @deprecated 4.11.10 Use Use WPMUDEV_Dashboard::$settings->get_transient().
*/
public function get_transient( $name, $prefix = true ) {
return WPMUDEV_Dashboard::$settings->get_transient( $name, $prefix );
}
/**
* Updates the value of a plugin transient.
* The plugins option-prefix is automatically added to the transient name.
*
* Use this function instead of direct access via update_site_option()
*
* @since 4.0.0
*
* @param string $name The transient name.
* @param mixed $value The new transient value.
* @param int $expiration Time until expiration. Default: No expiration.
*
* @deprecated 4.11.10 Use Use WPMUDEV_Dashboard::$settings->set_transient().
*/
public function set_transient( $name, $value, $expiration = 0 ) {
WPMUDEV_Dashboard::$settings->set_transient( $name, $value, $expiration );
}
/**
* Returns a usermeta value of the current user.
*
* @since 4.0.0
*
* @param string $name The meta-key.
*
* @return mixed The meta-value.
*/
public function get_usermeta( $name ) {
$user_id = get_current_user_id();
$value = get_user_meta( $user_id, $name, true );
return $value;
}
/**
* Updates a usermeta value of the current user.
*
* @since 4.0.0
*
* @param string $name The transient name.
* @param mixed $value The new transient value.
*/
public function set_usermeta( $name, $value ) {
$user_id = get_current_user_id();
update_user_meta( $user_id, $name, $value );
}
/**
* Log out current WPMUDEV user from the local site and erase all cached
* data.
* After logout, the user is redirected to the login page.
*
* @since 4.0.5
*
* @param bool $redirect If set to false, the user will not be redirected
* to the login page. Used for remote logout via ajax handler.
*/
public function logout( $redirect = true ) {
// Prevent infinite loops...
if ( ! WPMUDEV_Dashboard::$api->has_key() ) {
return;
}
// can't logout if define is being used. https://incsub.atlassian.net/browse/WDD-570.
if ( defined( 'WPMUDEV_APIKEY' ) && WPMUDEV_APIKEY ) {
return;
}
WPMUDEV_Dashboard::$api->hub_unsync();
// whatever happen in the API, we won't make it harder for you, your intention is simple: logout
// so regardless what hub_unsync response was, we will clean our self up
// caveats: when hub_unsync is actually failed in the Hub, this means your site possible still visible there
// in that case, feel free to contact our support at https://wpmudev.com/.
WPMUDEV_Dashboard::$api->set_key( '' ); // this has to be first, so any hook that potentially calls API and requires key won't be triggered.
WPMUDEV_Dashboard::$settings->reset();
WPMUDEV_Dashboard::$settings->set( 'membership_data', array() );
WPMUDEV_Dashboard::$settings->set( 'connected_admin', 0, 'general' );
if ( $redirect ) {
// Directly redirect to login page.
$urls = WPMUDEV_Dashboard::$ui->page_urls;
WPMUDEV_Dashboard::$ui->redirect_to( $urls->dashboard_url );
}
}
/**
* Converts the given date-time string or timestamp from GMT to the local
* WordPress timezone.
*
* @since 4.0.0
*
* @param string|int $time Either a date-time expression or timestamp.
*
* @return int The timestamp in local WordPress timezone.
*/
public function to_localtime( $time ) {
if ( is_numeric( $time ) ) {
$gmt_timestamp = intval( $time );
} else {
$gmt_timestamp = strtotime( $time );
}
if ( ! $time ) {
return 0;
}
$string = date( 'Y-m-d H:i:s', $gmt_timestamp ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$tz = get_option( 'timezone_string' ); // In Multisite networks this option is from the main blog.
if ( $tz ) {
$datetime = date_create( $string, new DateTimeZone( 'UTC' ) );
if ( ! $datetime ) {
return 0;
}
try {
$datetime->setTimezone( new DateTimeZone( $tz ) );
$localtime = strtotime( $datetime->format( 'Y-m-d H:i:s' ) );
} catch ( Throwable $ex ) {
$localtime = $gmt_timestamp; // fallback to gmt timestamp.
}
} else {
if ( ! preg_match( '#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches ) ) {
return 0;
}
$gmt_offset = get_option( 'gmt_offset' ); // In Multisite networks this option is from the main blog.
$string_time = gmmktime( $matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1] );
$localtime = $string_time + $gmt_offset * HOUR_IN_SECONDS;
}
return $localtime;
}
/**
* The proper way to get the array of locally installed products from cache.
*
* @since 1.0.0
*
* @param int $project_id Optional. If set then a single project array
* will be returned. Default: Return full project list.
*
* @return array
*/
public function get_cached_projects( $project_id = null ) {
$projects = WPMUDEV_Dashboard::$settings->get_transient( 'local_projects' );
if ( ! $projects || ! is_array( $projects ) ) {
// Set param to true to avoid infinite loop.
$projects = $this->scan_fs_local_projects();
if ( is_array( $projects ) ) {
// Save to be able to check for changes later.
WPMUDEV_Dashboard::$settings->set_transient(
'local_projects',
$projects,
5 * MINUTE_IN_SECONDS
);
}
}
/**
* Override installed WPMU DEV projects.
*
* @since 5.0.0
*
* @param array|false $res
* @param ?int $project_id
*/
$projects = apply_filters( 'wpmudev_dashboard_get_cached_projects', $projects, $project_id );
if ( $project_id ) {
$res = $projects[ $project_id ] ?? false;
} else {
$res = $projects;
}
return $res;
}
/**
* Returns lots of details about the specified project.
*
* @since 4.0.0
*
* @param int $pid The Project ID.
* @param bool $fetch_full Optional. If true, then even potentially
* time-consuming preparation is done.
* e.g. load changelog via API.
*
* @return object Details about the project.
*/
public function get_project_info( $pid, $fetch_full = false ) {
$pid = intval( $pid );
$is_network_admin = is_multisite(); // If multisite we only ever do things in network admin.
$urls = WPMUDEV_Dashboard::$ui->page_urls;
/**
* Short-circuit project info.
*
* @since 5.0.0
*
* @param object $pre The project info.
* @param int $pid The project ID.
* @param bool $fetch_full Whether to fetch full project info.
*/
$pre = apply_filters( 'wpmudev_dashboard_pre_get_project_info', null, $pid, $fetch_full );
if ( is_object( $pre ) ) {
return clone $pre;
}
if ( ! is_array( self::$cache_project_info ) ) {
self::$cache_project_info = array();
}
// build data if it's not cached or we need changelog and the changelog is missing from cache.
if ( ! isset( self::$cache_project_info[ $pid ] ) || ( $fetch_full && ! count( self::$cache_project_info[ $pid ]->changelog ) ) ) {
$res = (object) array(
'pid' => $pid,
'type' => '', // Possible: 'plugin' or 'theme'.
'special' => false, // Possible values: false, 'dropin' or 'muplugin'.
'name' => '', // Project name.
'path' => '', // Full path to main project file.
'filename' => '', // Filename, relative to plugins/themes dir.
'slug' => '', // Slug used for updates.
'version_latest' => '0.0',
'version_installed' => '0.0',
'has_update' => false, // Is new version available?
'can_update' => false, // User has permission to update?
'can_activate' => false, // User has permission to activate/deactivate?
'can_autoupdate' => false, // If plugin should auto-update?
'is_compatible' => true, // Site has all requirements to install project?
'incompatible_reason' => '', // If is_compatible is false.
'requires_min_php' => WPMUDEV_Dashboard::$upgrader->min_php, // Minimum PHP version required.
'need_upfront' => false, // Only used by themes.
'is_installed' => false, // Installed on current site?
'is_active' => false, // WordPress state, i.e. plugin activated?
'is_hidden' => false, // Projects can be hidden via API.
'is_licensed' => false, // User has license to use this project?
'is_addon' => false,
'default_order' => 0,
'downloads' => 0,
'popularity' => 0,
'release_stamp' => 0,
'update_stamp' => 0,
'info' => '',
'description' => '',
'url' => (object) array(
'instructions' => '',
'config' => '',
'activate' => '',
'deactivate' => '',
'install' => '',
'update' => '',
'download' => '',
'website' => '',
'thumbnail' => '',
'thumbnail_square' => '',
'icon' => '',
'banner_1x' => '',
'banner_2x' => '',
'video' => '',
'infos' => '',
),
'changelog' => array(),
'features' => array(),
'tags' => array(),
'screenshots' => array(),
'free_version_slug' => '',
);
$remote = WPMUDEV_Dashboard::$api->get_project_data( $pid );
if ( empty( $remote ) ) {
// can't return "false", here as Forminator atm `clone` the return value
// so if it ended up in this case, it would clone a boolean false, which will fatal error
// making stdClass will avoid fatal error, BUT it means the further check can't simply use `empty($return)` anymore
// it has to also check `empty($return->pid)` instead.
self::$cache_project_info[ $pid ] = new stdClass();
return clone self::$cache_project_info[ $pid ];
}
$local = WPMUDEV_Dashboard::$site->get_cached_projects( $pid );
$system_projects = WPMUDEV_Dashboard::$site->get_system_projects();
// General details.
$res->type = ( 'theme' === $remote['type'] ? 'theme' : 'plugin' );
$res->name = $remote['name'];
$res->info = wp_strip_all_tags( $remote['short_description'] );
$res->description = $remote['long_description'] ?? '';
$res->version_latest = $remote['version'];
$res->features = $remote['features'];
$res->default_order = isset( $remote['_order'] ) ? intval( $remote['_order'] ) : 0;
$res->downloads = intval( $remote['downloads'] );
$res->popularity = intval( $remote['popularity'] );
$res->release_stamp = intval( $remote['released'] );
$res->update_stamp = intval( $remote['updated'] );
$res->requires_min_php = empty( $remote['requires_min_php'] ) ? WPMUDEV_Dashboard::$upgrader->min_php : $remote['requires_min_php'];
$res->is_addon = $remote['is_plugin_addon'] ?? false;
// Project tags.
if ( 'plugin' === $res->type ) {
$tags = WPMUDEV_Dashboard::$ui->tags_data( 'plugin' );
} else {
$tags = WPMUDEV_Dashboard::$ui->tags_data( 'theme' );
}
foreach ( $tags as $tid => $tag ) {
$tag['pids'] = array_map( 'intval', $tag['pids'] );
if ( ! in_array( (int) $pid, $tag['pids'], true ) ) {
continue;
}
$res->tags[ $tid ] = $tag['name'];
}
// Status details.
$res->can_update = WPMUDEV_Dashboard::$upgrader->user_can_install( $pid );
$res->is_licensed = WPMUDEV_Dashboard::$upgrader->user_can_install( $pid, true );
if ( 'theme' === $res->type ) {
$res->need_upfront = $this->is_upfront_theme( $pid );
} elseif ( $this->id_upfront_builder === (int) $pid ) { // the upfront builder plugin requires Upfront theme.
$res->need_upfront = true;
}
$res->is_installed = WPMUDEV_Dashboard::$upgrader->is_project_installed( $pid );
$res->can_autoupdate = ( 1 === (int) $remote['autoupdate'] ); // this has nothing to do with permissions, just project capability.
$res->is_compatible = WPMUDEV_Dashboard::$upgrader->is_project_compatible( $pid, $incompatible_reason );
// Plugin can be active, even if not licensed:
// E.g. it was installed and then the admin logged out from WPMU DEV
// Dashboard, or the API-Key was changed from the Hub, ...
if ( $res->is_installed ) {
if ( ! empty( $local['name'] ) ) {
$res->name = $local['name'];
}
if ( 'muplugin' === $local['type'] ) {
$res->special = $local['type'];
} elseif ( 'dropin' === $local['type'] ) {
$res->special = $local['type'];
}
$res->path = $local['path'];
$res->filename = $local['filename'];
$res->slug = $local['slug'];
$res->version_installed = $local['version'];
$res->has_update = WPMUDEV_Dashboard::$upgrader->is_update_available( $pid );
if ( 'plugin' === $res->type ) {
if ( ! function_exists( 'is_plugin_active' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( $is_network_admin ) {
$res->is_active = is_plugin_active_for_network( $res->filename );
} else {
$res->is_active = is_plugin_active( $res->filename );
}
} elseif ( 'theme' === $res->type ) {
if ( $is_network_admin ) {
$allowed_themes = get_site_option( 'allowedthemes' );
$res->is_active = ! empty( $allowed_themes[ $res->slug ] );
} else {
$res->is_active = get_option( 'stylesheet' ) === $res->slug;
}
}
}
if ( in_array( (int) $pid, $system_projects, true ) ) {
// Hardcoded by plugin, those are always hidden!
$res->is_hidden = true;
} elseif ( $res->is_installed ) {
// Installed projects are always visible.
$res->is_hidden = false;
} elseif ( 'theme' === $res->type && $this->is_legacy_theme( $pid ) ) {
// Hide all non-installed legacy themes.
$res->is_hidden = true;
} else {
// Project is not installed, then use flag from API.
$res->is_hidden = ! $remote['active'];
}
if ( 'plugin' === $res->type ) {
if ( $res->special ) {
// MU-Plugins and Dropins cannot be (de)activated.
$res->can_activate = false;
} elseif ( $is_network_admin ) {
$res->can_activate = current_user_can( 'manage_network_plugins' );
} else {
$res->can_activate = current_user_can( 'activate_plugins' );
}
} elseif ( 'theme' === $res->type ) {
if ( $is_network_admin ) {
$res->can_activate = current_user_can( 'manage_network_themes' );
} else {
$res->can_activate = current_user_can( 'switch_themes' );
}
}
// URLs.
$res->url->website = esc_url( $remote['url'] );
if ( ! empty( $remote['thumbnail_large'] ) ) {
$res->url->thumbnail = esc_url( $remote['thumbnail_large'] );
} else {
$res->url->thumbnail = esc_url( $remote['thumbnail'] );
}
if ( ! empty( $remote['thumbnail_square'] ) ) {
$res->url->thumbnail_square = esc_url( $remote['thumbnail_square'] );
} else {
$res->url->thumbnail_square = esc_url( $remote['thumbnail'] );
}
// Project icon.
if ( ! empty( $remote['icon'] ) ) {
$res->url->icon = esc_url( $remote['icon'] );
} else {
$res->url->icon = $res->url->thumbnail_square;
}
// Project banner.
$res->url->banner_1x = $res->url->thumbnail;
$res->url->banner_2x = $res->url->thumbnail;
if ( ! empty( $remote['banner_1x'] ) ) {
$res->url->banner_1x = esc_url( $remote['banner_1x'] );
}
if ( ! empty( $remote['banner_2x'] ) ) {
$res->url->banner_2x = esc_url( $remote['banner_2x'] );
}
$res->url->video = esc_url( $remote['video'] );
$res->url->instructions = WPMUDEV_Dashboard::$api->rest_url( 'usage/' . $pid );
if ( 'plugin' === $res->type ) {
if ( $is_network_admin ) {
if ( empty( $remote['ms_config_url'] ) ) {
// In case if the plugin doesn't have network settings but have blog settings.
if ( ! empty( $remote['wp_config_url'] ) ) {
$res->url->config = esc_url( admin_url( $remote['wp_config_url'] ) );
}
} else {
$res->url->config = esc_url( network_admin_url( $remote['ms_config_url'] ) );
}
} elseif ( ! empty( $remote['wp_config_url'] ) ) {
$res->url->config = esc_url( admin_url( $remote['wp_config_url'] ) );
}
}
$res->url->install = WPMUDEV_Dashboard::$upgrader->auto_install_url( $pid );
$res->url->download = esc_url( $remote['url'] );
if ( ! $res->special ) {
// I.e. only if plugin is no dropin/muplugin.
$res->url->update = WPMUDEV_Dashboard::$upgrader->auto_update_url( $pid );
}
if ( ! $res->is_compatible ) {
switch ( $incompatible_reason ) {
case 'multisite':
$res->incompatible_reason = __( 'Requires Multisite', 'wpmudev' );
break;
case 'buddypress':
$res->incompatible_reason = __( 'Requires BuddyPress', 'wpmudev' );
break;
case 'php':
/* translators: %s: Required minimum PHP version. */
$res->incompatible_reason = sprintf( __( 'Requires PHP %s or above', 'wpmudev' ), $res->requires_min_php );
break;
default:
$res->incompatible_reason = __( 'Incompatible', 'wpmudev' );
break;
}
}
// When not logged in, the project-ID is passed as URL param!
$pid_sep = WPMUDEV_Dashboard::$api->has_key() ? '#' : '&';
if ( 'plugin' === $res->type ) {
$res->url->infos = $urls->plugins_url . $pid_sep . 'pid=' . $pid;
$res->url->deactivate = 'plugins.php?action=deactivate&plugin=' . rawurlencode( $res->filename );
$res->url->activate = 'plugins.php?action=activate&plugin=' . rawurlencode( $res->filename );
if ( $is_network_admin ) {
$res->url->deactivate = network_admin_url( $res->url->deactivate );
$res->url->activate = network_admin_url( $res->url->activate );
} else {
$res->url->deactivate = admin_url( $res->url->deactivate );
$res->url->activate = admin_url( $res->url->activate );
}
$res->url->deactivate = wp_nonce_url( $res->url->deactivate, 'deactivate-plugin_' . $res->filename );
$res->url->activate = wp_nonce_url( $res->url->activate, 'activate-plugin_' . $res->filename );
} elseif ( 'theme' === $res->type ) {
$res->url->infos = $urls->themes_url . $pid_sep . 'pid=' . $pid;
if ( $is_network_admin ) {
/*
* In Network-Admin following theme-actions are disabled:
* - Activate
* - Configure
*/
$res->url->activate = false;
$res->url->config = false;
} else {
$res->url->activate = wp_nonce_url(
'themes.php?action=activate&template=' . rawurlencode( $res->filename ) . '&stylesheet=' . rawurlencode( $res->filename ),
'switch-theme_' . $res->filename
);
if ( $res->need_upfront ) {
$res->url->config = home_url( '/?editmode=true' );
} else {
$return_url = rawurlencode( WPMUDEV_Dashboard::$ui->page_urls->themes_url );
$res->url->config = admin_url( 'customize.php?return=' . $return_url );
}
}
}
$res->screenshots = $remote['screenshots'];
$res->free_version_slug = $remote['free_version_slug'];
// Temporary fix for Branda and Beehive missing or having invalid free version slugs.
if ( 9135 === $pid ) {
$res->free_version_slug = 'branda-white-labeling/ultimate-branding.php';
}
if ( 51 === $pid ) {
$res->free_version_slug = 'beehive-analytics/beehive-analytics.php';
}
if ( 2097296 === $pid ) {
$res->free_version_slug = 'forminator/forminator.php';
}
// Temporary fix end.
// Performance: Only fetch changelog if needed.
if ( $fetch_full ) {
$res->changelog = WPMUDEV_Dashboard::$api->get_changelog(
$pid,
$res->version_latest
);
}
self::$cache_project_info[ $pid ] = $res;
}
// Following flags are not cached.
if ( self::$cache_project_info[ $pid ] && is_object( self::$cache_project_info[ $pid ] ) && isset( self::$cache_project_info[ $pid ]->pid ) ) {
self::$cache_project_info[ $pid ]->is_network_admin = $is_network_admin;
} else {
self::$cache_project_info[ $pid ] = new stdClass();
}
return clone self::$cache_project_info[ $pid ];
}
/**
* Check if a given theme project id is an Upfront theme.
*
* @since 3.0.0
*
* @param int $project_id The project to check.
*
* @return bool
*/
public function is_upfront_theme( $project_id ) {
if ( (int) $project_id === $this->id_upfront ) {
return false;
}
if ( $project_id <= $this->id_legacy_themes ) {
return false;
}
return true;
}
/**
* Check if a given theme project id is a legacy theme.
*
* @since 3.0.0
*
* @param int $project_id The project to check.
*
* @return bool
*/
public function is_legacy_theme( $project_id ) {
if ( $project_id > $this->id_legacy_themes ) {
return false;
}
return true;
}
/**
* Check if root Upfront project is installed.
*
* @since 3.0.0
* @return bool
*/
public function is_upfront_installed() {
$data = $this->get_cached_projects( $this->id_upfront );
return ( ! empty( $data ) );
}
/**
* Check if a child Upfront project is installed.
*
* @since 3.0.0
* @return bool
*/
public function is_upfront_theme_installed() {
$result = false;
$local_projects = $this->get_cached_projects();
foreach ( $local_projects as $project_id => $project ) {
// Quit on first theme installed greater than legacy threshold.
if ( 'theme' === $project['type'] && $this->is_upfront_theme( $project_id ) ) {
$result = true;
break;
}
}
return $result;
}
/**
* Checks if the current user is in the list of allowed users of the Dashboard.
* Allows for multiple users allowed in define, e.g. in this format:
*
* <code>
* define("WPMUDEV_LIMIT_TO_USER", "1, 10, 15");
* </code>
*
* @since 1.0.0
*
* @param int $user_id Optional. If empty then the current user-ID is used.
*
* @return bool
*/
public function allowed_user( $user_id = null ) {
// If this is a remote call from WPMUDEV remote dashboard then allow.
if ( ! $user_id && defined( 'WPMUDEV_IS_REMOTE' ) && WPMUDEV_IS_REMOTE ) {
return true;
}
// Balk if this is called too early.
if ( ! $user_id && ! did_action( 'set_current_user' ) ) {
return false;
}
if ( empty( $user_id ) ) {
/*
* @todo calling this too soon bugs out in some wp installs
* http://wpmudev.com/forums/topic/urgenti-lost-permission-after-upgrading#post-227543
*/
$user_id = get_current_user_id();
}
if ( ! $user_id ) {
return false;
}
$allowed = $this->get_allowed_users( true );
$allowed = is_array( $allowed ) ? $allowed : array();
$allowed = array_map( 'intval', $allowed );
return in_array( (int) $user_id, $allowed, true );
}
/**
* Grant access to the WPMU DEV Dashboard to a new admin user.
*
* @since 4.0.0
*
* @param int $user_id The user to add.
*
* @return bool True on success, false on failure.
*/
public function add_allowed_user( $user_id ) {
$user = get_userdata( $user_id );
if ( ! $user || ! is_a( $user, 'WP_User' ) ) {
// User not found.
return false;
}
$need_cap = 'manage_options';
if ( is_multisite() ) {
$need_cap = 'manage_network_options';
}
if ( ! $user->has_cap( $need_cap ) ) {
// User is no admin.
return false;
}
$allowed = $this->get_allowed_users_from_settings();
if ( in_array( (int) $user_id, $allowed, true ) ) {
// User was already added.
return false;
}
$allowed[] = $user_id;
WPMUDEV_Dashboard::$settings->set( 'limit_to_user', $allowed, 'general' );
/**
* Action hook to trigger when a admin user is added to permissions.
*
* @since 4.11.18
*
* @param int $user_id Added user ID.
* @param array $allowed Allowed user IDs.
*/
do_action( 'wpmudev_after_add_allowed_user', $user_id, $allowed );
return true;
}
/**
* Get allowed users from settings / options.
*
* @return array
*/
public function get_allowed_users_from_settings() {
$allowed = WPMUDEV_Dashboard::$settings->get( 'limit_to_user', 'general', array() );
$allowed = is_array( $allowed ) ? $allowed : ( is_numeric( $allowed ) ? array( $allowed ) : array() );
$allowed = array_map( 'intval', $allowed );
$allowed = array_filter( $allowed );
return array_values( $allowed );
}
/**
* Remove access to the WPMU DEV Dashboard from another admin user.
*
* @since 4.0.0
*
* @param int $user_id The user to remove.
*
* @return bool True on success, false on failure.
*/
public function remove_allowed_user( $user_id ) {
$allowed = $this->get_allowed_users_from_settings();
if ( empty( $allowed ) ) {
// The allowed-list is still empty.
return false;
}
$key = array_search( (int) $user_id, $allowed, true );
if ( false === $key ) {
// User not found in the allowed-list.
return false;
}
unset( $allowed[ $key ] );
$allowed = array_values( $allowed );
WPMUDEV_Dashboard::$settings->set( 'limit_to_user', $allowed, 'general' );
/**
* Action hook to trigger when a admin user is removed from permissions.
*
* @since 4.11.18
*
* @param int $user_id Removed user ID.
* @param array $allowed Allowed user IDs.
*/
do_action( 'wpmudev_after_remove_allowed_user', $user_id, $allowed );
return true;
}
/**
* Get a human readable list of users with allowed permissions for the
* Dashboard.
*
* @since 1.0.0
*
* @param bool $id_only Return only user-IDs or full usernames.
*
* @return array|bool
*/
public function get_allowed_users( $id_only = false ) {
$result = false;
if ( WPMUDEV_LIMIT_TO_USER ) {
// Hardcoded list of users.
if ( is_array( WPMUDEV_LIMIT_TO_USER ) ) {
$allowed = WPMUDEV_LIMIT_TO_USER;
} else {
$allowed = explode( ',', WPMUDEV_LIMIT_TO_USER );
$allowed = array_map( 'trim', $allowed );
}
$allowed = array_map( 'intval', $allowed );
} else {
$changed = false;
// Default: Allow users based on DB settings.
$allowed = WPMUDEV_Dashboard::$settings->get( 'limit_to_user', 'general' );
if ( $allowed ) {
$allowed_from_settings = $this->get_allowed_users_from_settings();
if ( $allowed !== $allowed_from_settings ) {
$allowed = $allowed_from_settings;
$changed = true;
}
} else {
// If not set, then add current user as allowed user.
$cur_user_id = get_current_user_id();
if ( $cur_user_id ) {
$allowed = array( $cur_user_id );
$changed = true;
} else {
$allowed = array();
}
}
// Sanitize allowed users after login to Dashboard, so we can
// react to changes in the user capabilities.
if ( ! empty( $allowed ) && WPMUDEV_Dashboard::$api->has_key() ) {
$need_cap = 'manage_options';
if ( is_multisite() ) {
$need_cap = 'manage_network_options';
}
// Remove invalid users from the allowed-users-list.
foreach ( $allowed as $key => $user_id ) {
$user = get_userdata( $user_id );
if ( ! $user || ! is_a( $user, 'WP_User' ) ) {
unset( $allowed[ $key ] );
$changed = true;
} elseif ( ! $user->has_cap( $need_cap ) ) {
unset( $allowed[ $key ] );
$changed = true;
}
}
if ( $changed ) {
WPMUDEV_Dashboard::$settings->set( 'limit_to_user', $allowed, 'general' );
}
}
}
if ( $id_only ) {
$result = $allowed;
} else {
$result = array();
foreach ( $allowed as $user_id ) {
$user_info = get_userdata( $user_id );
if ( $user_info ) {
$result[] = array(
'id' => $user_id,
'name' => $user_info->display_name,
'email' => $user_info->user_email,
'first_name' => $user_info->user_firstname,
'last_name' => $user_info->user_lastname,
'username' => $user_info->user_login,
'is_me' => (int) get_current_user_id() === (int) $user_id,
'profile_link' => get_edit_user_link( $user_id ),
);
}
}
}
return $result;
}
/**
* Get the list of admin users available to add.
*
* Get all admin capable users which is not added yet
* to list.
*
* @since 4.11.2
* @since 4.11.10 Added $limit argument.
*
* @param int $limit Limit no. of users to retrieve.
* @param bool $exclude_existing Exclude existing admins.
*
* @return array
*/
public function get_available_users( $limit = 0, $exclude_existing = true ) {
// We need only IDs for now.
$args = array(
'fields' => array(
'ID',
'display_name',
'user_login',
'user_email',
),
'count_total' => false,
);
// Exclude already allowed users.
if ( $exclude_existing ) {
// Get already allowed users.
$allowed = $this->get_allowed_users( true );
if ( ! empty( $allowed ) ) {
$args['exclude'] = $allowed;
}
}
// To get from all blogs on multisite.
if ( is_multisite() ) {
$args['blog_id'] = 0;
}
// Filter only admins.
if ( is_multisite() ) {
$args['login__in'] = get_super_admins();
} else {
$args['role'] = 'administrator';
}
/**
* Filter to adjust users query limit.
*
* @since 4.11.10
*
* @param int $limit Limit.
*/
$limit = apply_filters( 'wpmudev_dashboard_get_available_users_limit', $limit );
// Limit no. of users for performance.
if ( ! empty( $limit ) ) {
$args['number'] = (int) $limit;
}
// Get user IDs.
return get_users( $args );
}
/**
* Detect if this is a development site running on a private/loopback IP
*
* @return bool
*/
public function is_localhost() {
$loopbacks = array( '127.0.0.1', '::1' );
if ( in_array( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ), $loopbacks, true ) ) {
return true;
}
if ( ! filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ), FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE ) ) {
return true;
}
return false;
}
/**
* Check user permissions to see if we can install this project.
*
* NOTE: Not sure why this duplicate exist. Since it's a public function we can not
* remove it for now. So deprecating.
*
* @since 1.0.0
*
* @param int $project_id The project to check.
* @param bool $only_license Skip permission check, only validate license.
*
* @return bool
* @deprecated 4.11.17
*/
public function user_can_install( $project_id, $only_license = false ) {
return WPMUDEV_Dashboard::$upgrader->user_can_install( $project_id, $only_license );
}
/**
* Returns a list of internal/hidden/deprecated projects.
*
* @since 4.0.0
* @return array
*/
public function get_system_projects() {
$list = array(
// Upfront parent is hidden.
WPMUDEV_Dashboard::$site->id_upfront,
);
return $list;
}
/*
* *********************************************************************** *
* * INTERNAL ACTION HANDLERS
* *********************************************************************** *
*/
/**
* Check for any compatibility issues or important updates and display a
* notice if found.
*
* @since 4.0.0
*/
public function compatibility_warnings() {
/**
* Whether to check for compatibility.
*
* @since 5.0.0
*/
$check_compatibility = apply_filters( 'wpmudev_dashboard_check_compatibility', true );
if ( ! $check_compatibility ) {
return;
}
if ( $this->is_upfront_theme_installed() && ! $this->is_upfront_installed() ) {
// Upfront child theme is installed but not parent theme is missing:
// Only display this on the WP Dashboard page.
$upfront = $this->get_project_info( $this->id_upfront );
if ( is_object( $upfront ) && ! empty( $upfront->pid ) ) {
do_action(
'wpmudev_override_notice',
__( '<b>The Upfront parent theme is missing!</b><br>Please install it to use your Upfront child themes', 'wpmudev' ),
'<a href="' . $upfront->url->install . '" class="button button-primary">Install Upfront</a>'
);
}
}
}
/**
* Does a filesystem scan for local plugins/themes and caches it. If any
* changes found it will trigger remote api check and calculate upgrades as well.
*
* @since 1.0.0
* @since 5.0.0 Allow full sync to be outside controlled
*
* @param string $check Either 'local' or 'remote'. Local will only scan
* the local FS for changes. Remote will also query the API
* and schedule updates.
* @param bool $full_sync Whether Force the full sync.
*
* @return array
*/
public function refresh_local_projects( $check = 'remote', $full_sync = false ) {
// 1. Scan local FS to find projects.
$local_projects = $this->scan_fs_local_projects();
// 2. Load the cached project-list from DB.
$saved_local_projects = $this->get_cached_projects();
// Check for changes.
$md5_db = md5( wp_json_encode( $saved_local_projects ) );
$md5_fs = md5( wp_json_encode( $local_projects ) );
if ( 'remote' === $check || ! hash_equals( $md5_db, $md5_fs ) ) {
self::reset_memoizer();
WPMUDEV_Dashboard::$settings->set_transient(
'local_projects',
$local_projects,
5 * MINUTE_IN_SECONDS
);
WPMUDEV_Dashboard::$settings->set( 'updates_available', false );
// phpcs:disable WordPress.Security.NonceVerification.Recommended
// check if is manual check for updates.
if ( isset( $_REQUEST['action'] ) && 'check-updates' === $_REQUEST['action'] ) {
// force hubsync.
$full_sync = true;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
WPMUDEV_Dashboard::$api->hub_sync( $local_projects, $full_sync );
// Recalculate upgrades with current/updated data.
WPMUDEV_Dashboard::$api->calculate_upgrades( $local_projects );
}
return $local_projects;
}
/**
* Used to call refresh_local_projects from a hook (strip passed arguments)
*
* @since 1.0.0
* @internal Action hook
*/
public function refresh_local_projects_wrapper() {
/**
* Whether to do refresh local projects from hooks.
*
* @since 5.0.0
*/
$do_refresh = apply_filters( 'wpmudev_dashboard_refresh_local_projects_wrapper', true );
if ( ! $do_refresh ) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['force-check'] ) ) {
// phpcs:enable WordPress.Security.NonceVerification.Recommended
self::$refresh_shutdown_flag = false;
WPMUDEV_Dashboard::$api->refresh_projects_data();
$this->refresh_local_projects( 'remote' );
} else {
$this->refresh_local_projects( 'local' );
}
}
/**
* This handler is called right before starting an upgrade/installation
* process, it makes sure that the upgrader will get uncached and
* up-to-date details.
*
* @since 4.1.0
*/
public function clear_local_file_cache() {
self::$cache_project_info = false;
WPMUDEV_Dashboard::$settings->set_transient( 'local_projects', false );
}
/**
* This handler is called right after a plugin was installed or updated.
* It instructs the dashboard to flush all caches (i.e. filesystem is
* scanned again, the transient is re-generated, ...)
*
* @since 4.0.7
*
* @param object $upgrader Upgrader class.
* @param array $args Hook arguments.
*/
public function after_local_files_changed( $upgrader, $args ) {
self::reset_memoizer();
$this->clear_local_file_cache();
// Recalculate available translation updates.
if ( isset( $args['type'], $args['bulk'] ) && 'translation' === $args['type'] && $args['bulk'] ) {
WPMUDEV_Dashboard::$api->calculate_translation_upgrades( true );
}
}
/**
* Something happened that we need to send data to DEV, so schedule it to run on shutdown hook
*
* @since 4.2
*/
public function schedule_shutdown_refresh() {
self::$refresh_shutdown_flag = true;
}
/**
* Get the state of refresh shutdown flag.
*
* @since 5.0.0
*
* @return bool
*/
public function get_refresh_shutdown_flag(): bool {
return self::$refresh_shutdown_flag;
}
/**
* Reset the state of refresh shutdown flag.
*
* @since 5.0.0
*
* @return void
*/
public function reset_refresh_shutdown_flag() {
self::$refresh_shutdown_flag = false;
}
/**
* Sends latest data to DEV if schedule at end of page load
*/
public function shutdown_refresh() {
if ( self::$refresh_shutdown_flag && ! defined( 'WPMUDEV_REMOTE_SKIP_SYNC' ) ) {
WPMUDEV_Dashboard::$site->refresh_local_projects( 'remote' );
}
}
/**
* Scans all folder locations and compiles a list of WPMU DEV plugins and
* themes and header data.
* Also saves 133 theme pack themes into option for later use.
*
* @since 1.0.0
* @return array Local projects
* @internal
*/
protected function scan_fs_local_projects() {
$projects = array();
// ----------------------------------------------------------------------------------
// Plugins directory.
// ----------------------------------------------------------------------------------
$plugins_root = WP_PLUGIN_DIR;
if ( empty( $plugins_root ) ) {
$plugins_root = ABSPATH . 'wp-content/plugins';
}
$items = $this->find_project_files( $plugins_root, '.php', true );
foreach ( $items as $item ) {
if ( isset( $projects[ $item['pid'] ] ) ) {
continue;
}
$item['type'] = 'plugin';
$projects[ $item['pid'] ] = $item;
}
// ----------------------------------------------------------------------------------
// mu-plugins directory.
// ----------------------------------------------------------------------------------
$mu_plugins_root = WPMU_PLUGIN_DIR;
if ( empty( $mu_plugins_root ) ) {
$mu_plugins_root = ABSPATH . 'wp-content/mu-plugins';
}
$items = $this->find_project_files( $mu_plugins_root, '.php', false );
foreach ( $items as $item ) {
if ( isset( $projects[ $item['pid'] ] ) ) {
continue;
}
$item['type'] = 'muplugin';
$projects[ $item['pid'] ] = $item;
}
// ----------------------------------------------------------------------------------
// wp-content directory.
// ----------------------------------------------------------------------------------
$content_plugins_root = WP_CONTENT_DIR;
if ( empty( $content_plugins_root ) ) {
$content_plugins_root = ABSPATH . 'wp-content';
}
$items = $this->find_project_files( $content_plugins_root, '.php', false );
foreach ( $items as $item ) {
if ( isset( $projects[ $item['pid'] ] ) ) {
continue;
}
$item['type'] = 'dropin';
$projects[ $item['pid'] ] = $item;
}
// ----------------------------------------------------------------------------------
// Themes directory.
// ----------------------------------------------------------------------------------
$themes_root = WP_CONTENT_DIR . '/themes';
if ( empty( $themes_root ) ) {
$themes_root = ABSPATH . 'wp-content/themes';
}
$items = $this->find_project_files( $themes_root, '.css', true );
foreach ( $items as $item ) {
// Skip 133-Farm-Pack themes.
if ( (int) $item['pid'] === $this->id_farm133_themes ) {
continue;
}
// Skip child themes.
if ( false !== strpos( $item['filename'], '-child' ) ) {
continue;
}
$item['type'] = 'theme';
$item['slug'] = basename( dirname( $item['path'] ) );
$projects[ $item['pid'] ] = $item;
}
return $projects;
}
/**
* Returns an array of relevant files from the specified folder.
*
* @since 4.0.0
*
* @param string $path The absolute path to the base directory to scan.
* @param string $ext File extension to return (i.e. '.php' or '.css').
* @param bool $check_subdirs False will ignore files in sub-directories.
*
* @return array Details about all WPMU Projects found in the directory.
*/
protected function find_project_files( $path, $ext = '.php', $check_subdirs = true ) {
$files = array();
$items = array();
$h_dir = false;
$h_subdir = false;
$ext_len = strlen( $ext );
if ( is_dir( $path ) ) {
$h_dir = opendir( $path );
}
// phpcs:disable Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
while ( $h_dir && ( $file = readdir( $h_dir ) ) !== false ) {
if ( substr( $file, 0, 1 ) === '.' ) {
continue;
}
if ( is_dir( $path . '/' . $file ) ) {
if ( ! $check_subdirs ) {
continue;
}
$h_subdir = opendir( $path . '/' . $file );
while ( $h_subdir && ( $subfile = readdir( $h_subdir ) ) !== false ) {
if ( substr( $subfile, 0, 1 ) === '.' ) {
continue;
}
if ( ! is_readable( "$path/$file/$subfile" ) ) {
continue;
}
if ( substr( $subfile, -$ext_len ) === $ext ) {
$files[] = "$file/$subfile";
}
}
if ( $h_subdir ) {
closedir( $h_subdir );
}
} else {
if ( ! is_readable( "$path/$file" ) ) {
continue;
}
if ( substr( $file, -$ext_len ) === $ext ) {
$files[] = $file;
}
}
}
if ( $h_dir ) {
closedir( $h_dir );
}
// phpcs:disable Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
foreach ( $files as $file ) {
$data = $this->get_id_plugin( "$path/$file" );
if ( ! empty( $data['id'] ) ) {
$items[] = array(
'pid' => $data['id'],
'name' => $data['name'],
'filename' => $file,
'path' => "$path/$file",
'version' => $data['version'],
'slug' => 'wpmudev_install-' . $data['id'],
);
}
}
return $items;
}
/**
* Get our special WDP ID header line from the file.
*
* @since 1.0.0
*
* @param string $plugin_file Main file of the plugin.
*
* @return array Plugin details: name, id, version.
* @uses get_file_data()
* @internal
*/
protected function get_id_plugin( $plugin_file ) {
return get_file_data(
$plugin_file,
array(
'name' => 'Plugin Name',
'id' => 'WDP ID',
'version' => 'Version',
)
);
}
/**
* Hooks into the plugin update api to add our custom api data.
*
* @since 1.0.0
* @since 4.11.10 Added more data.
*
* @param object $result Default update-info provided by WordPress.
* @param string $action What action was requested (theme or plugin?).
* @param object $args Details used to build default update-info.
*
* @return object Modified theme/plugin update-info.
* @internal Action handler
*/
public function filter_plugin_update_info( $result, $action, $args ) {
global $wp_version, $pagenow;
// Is WordPress processing a plugin or theme? If not, stop.
if ( 'plugin_information' !== $action ) {
return $result;
}
// Get project ID.
$pid = str_replace( 'wpmudev_install-', '', $args->slug );
// Is the theme/plugin by WPMUDEV? If not, stop.
if ( ! is_numeric( $pid ) ) {
return $result;
}
// Get project data.
$project = WPMUDEV_Dashboard::$site->get_project_info( $pid, true );
// No need to continue if empty.
if ( empty( $project ) || empty( $project->changelog ) ) {
return $result;
}
$changelog = '';
foreach ( $project->changelog as $log ) {
// Should be a proper item.
if ( empty( $log ) || ! is_array( $log ) ) {
continue;
}
// Changelog version.
$changelog .= '<h4>' . $log['version'] . '</h4>';
// Changelog date.
$changelog .= '<p>' . __( 'Release Date', 'wpmudev' ) . ' - ' . date_i18n( get_option( 'date_format' ), $log['time'] ) . '</p>';
// Split by line break.
$notes = explode( "\n", $log['log'] );
if ( ! empty( $notes ) ) {
$changelog .= '<ul>';
foreach ( $notes as $note ) {
// Remove all unwanted tags.
$note = stripslashes( $note );
$note = preg_replace( '/(<br ?\/?>|<p>|<\/p>)/', '', $note );
$note = trim( preg_replace( '/^\s*(\*|\-)\s*/', '', $note ) );
$note = str_replace( array( '<', '>' ), array( '<', '>' ), $note );
$note = preg_replace( '/`(.*?)`/', '<code>\1</code>', $note );
// If empty, skip the item.
if ( empty( $note ) ) {
continue;
}
$changelog .= '<li>' . wp_kses_post( $note ) . '</li>';
}
$changelog .= '</ul>';
}
}
$result = (object) array(
'name' => $project->name,
'slug' => sanitize_title( $project->name ),
'version' => $project->version_installed,
'homepage' => $project->url->website,
'author' => '<a href="https://wpmudev.com/" target="_blank">WPMU DEV</a>',
'contributors' => array(
'wpmudev' => array(
'profile' => 'https://wpmudev.com/',
'avatar' => WPMUDEV_Dashboard::$site->plugin_url . 'static/images/wpmudev.png',
'display_name' => 'WPMU DEV',
),
),
'tested' => $wp_version,
'added' => gmdate( 'Y-m-d', $project->release_stamp ),
'last_updated' => gmdate( 'Y-m-d', $project->update_stamp ),
'sections' => array(
'description' => empty( $project->description ) ? $project->info : $project->description,
'changelog' => $changelog,
),
'icons' => array(
'default' => $project->url->thumbnail_square,
),
'banners' => array(
'low' => empty( $project->url->banner_1x ) ? $project->url->thumbnail : $project->url->banner_1x,
'high' => empty( $project->url->banner_2x ) ? $project->url->thumbnail : $project->url->banner_2x,
),
);
// Add download link only if not plugins page and updates page.
if ( ! in_array( $pagenow, array( 'plugins.php', 'update-core.php', 'plugin-install.php' ), true ) && $project->is_compatible ) {
$result->download_link = WPMUDEV_Dashboard::$api->rest_url_auth( 'install/' . $pid );
}
return $result;
}
/**
* Update the transient value of available plugin updates right before WordPress saves it to
* the database.
*
* @since 1.0.0
*
* @param object $value The transient value that will be saved.
*
* @return object Modified transient value.
* @internal Action hook
*/
public function filter_plugin_update_count( $value ) {
global $wp_version;
// if no key provided, update won't be doable anyway.
if ( ! WPMUDEV_Dashboard::$api->has_key() ) {
return $value;
}
$cur_wp_version = preg_replace( '/-.*$/', '', $wp_version );
if ( ! is_object( $value ) ) {
return $value;
}
if ( is_null( self::$memoized_local_projects ) ) {
self::$memoized_local_projects = WPMUDEV_Dashboard::$site->get_cached_projects();
}
self::$memoized_local_projects = is_array( self::$memoized_local_projects ) ? self::$memoized_local_projects : array();
// First remove all installed WPMUDEV plugins from the WP update data.
if ( isset( $value->response ) && is_array( $value->response ) ) {
foreach ( self::$memoized_local_projects as $local_project ) {
if ( 'plugin' !== $local_project['type'] ) {
continue;
}
if ( isset( $value->response[ $local_project['filename'] ] ) ) {
unset( $value->response[ $local_project['filename'] ] );
}
if ( isset( $value->no_update[ $local_project['filename'] ] ) ) {
unset( $value->no_update[ $local_project['filename'] ] );
}
}
}
if ( is_null( self::$memoized_plugin_updates ) ) {
self::$memoized_plugin_updates = array();
if ( is_null( self::$memoized_updates ) ) {
// Value of 'updates_available' is set by API `calculate_upgrades()`.
self::$memoized_updates = WPMUDEV_Dashboard::$settings->get( 'updates_available' );
if ( false === self::$memoized_updates ) {
self::$memoized_updates = WPMUDEV_Dashboard::$api->calculate_upgrades( self::$memoized_local_projects );
}
}
if ( is_array( self::$memoized_updates ) && count( self::$memoized_updates ) ) {
foreach ( self::$memoized_updates as $id => $plugin ) {
if ( 'theme' === $plugin['type'] ) {
continue;
}
if ( 2 === (int) $plugin['autoupdate'] ) {
continue;
}
$package = '';
$autoupdate = false;
$local = $this->get_cached_projects( $id );
// Do not continue if slug not found.
if ( empty( $local['slug'] ) ) {
continue;
}
$compatible = WPMUDEV_Dashboard::$upgrader->is_project_compatible( $id );
if ( $compatible && 1 === (int) $plugin['autoupdate'] && WPMUDEV_Dashboard::$api->has_key() ) {
$package = WPMUDEV_Dashboard::$api->rest_url_auth( 'download/' . $id );
} elseif ( $compatible && 119 === (int) $id ) {
// Public download url for Dashboard.
$package = WPMUDEV_Dashboard::$api->rest_url( 'download-dashboard' );
}
$thumb = $plugin['thumbnail'] ?? '';
// Build plugin class.
$object = (object) array(
'id' => "wpmudev/plugins/$id",
'slug' => $local['slug'],
'plugin' => $plugin['filename'],
'new_version' => $plugin['new_version'],
'url' => $plugin['url'],
'package' => $package,
'icons' => array(
'1x' => $thumb,
'default' => $thumb,
),
'autoupdate' => $autoupdate,
'tested' => $cur_wp_version,
);
self::$memoized_plugin_updates[ $plugin['filename'] ] = $object;
}
}
}
// Finally merge available WPMUDEV updates into default WP update data.
if ( ! empty( self::$memoized_plugin_updates ) ) {
if ( isset( $value->response ) && is_array( $value->response ) ) {
$value->response = array_merge( $value->response, self::$memoized_plugin_updates );
} else {
$value->response = self::$memoized_plugin_updates;
}
}
if ( is_null( self::$memoized_translation_updates ) ) {
self::$memoized_translation_updates = WPMUDEV_Dashboard::$api->calculate_translation_upgrades();
}
if ( ! empty( self::$memoized_translation_updates ) ) {
if ( isset( $value->translations ) && is_array( $value->translations ) ) {
// translations[] isn't keyed, so make sure to check duplicates.
$default_keyed_translations = array();
foreach ( $value->translations as $translation ) {
$key = implode(
'|',
array(
$translation['type'] ?? '',
$translation['slug'] ?? '',
$translation['language'] ?? '',
$translation['updated'] ?? '',
)
);
$default_keyed_translations[ $key ] = 1;
}
foreach ( self::$memoized_translation_updates as $translation_update ) {
$key = implode(
'|',
array(
$translation_update['type'] ?? '',
$translation_update['slug'] ?? '',
$translation_update['language'] ?? '',
$translation_update['updated'] ?? '',
)
);
if ( isset( $default_keyed_translations[ $key ] ) ) {
continue;
}
$value->translations[] = $translation_update;
}
} else {
$value->translations = self::$memoized_translation_updates;
}
}
return $value;
}
/**
* Update the transient value of available theme updates right after
* WordPress read it from the database.
* We add the WPMUDEV theme-updates to the default list of theme updates.
*
* @since 1.0.0
*
* @param object $value The transient value that will be saved.
*
* @return object Modified transient value.
* @internal Action hook
*/
public function filter_theme_update_count( $value ) {
global $wp_version;
// if no key provided, update won't be doable anyway.
if ( ! WPMUDEV_Dashboard::$api->has_key() ) {
return $value;
}
$cur_wp_version = preg_replace( '/-.*$/', '', $wp_version );
if ( ! is_object( $value ) ) {
return $value;
}
if ( is_null( self::$memoized_local_projects ) ) {
self::$memoized_local_projects = WPMUDEV_Dashboard::$site->get_cached_projects();
}
self::$memoized_local_projects = is_array( self::$memoized_local_projects ) ? self::$memoized_local_projects : array();
// First remove all installed WPMUDEV themes from the WP update data.
if ( isset( $value->response ) && is_array( $value->response ) ) {
foreach ( self::$memoized_local_projects as $local_project ) {
if ( 'theme' !== $local_project['type'] ) {
continue;
}
$theme_slug = dirname( $local_project['filename'] );
if ( isset( $value->response[ $theme_slug ] ) ) {
unset( $value->response[ $theme_slug ] );
}
if ( isset( $value->no_update[ $theme_slug ] ) ) {
unset( $value->no_update[ $theme_slug ] );
}
}
}
if ( is_null( self::$memoized_theme_updates ) ) {
self::$memoized_theme_updates = array();
// Value of 'updates_available' is set by API `calculate_upgrades()`.
if ( is_null( self::$memoized_updates ) ) {
// Value of 'updates_available' is set by API `calculate_upgrades()`.
self::$memoized_updates = WPMUDEV_Dashboard::$settings->get( 'updates_available' );
if ( false === self::$memoized_updates ) {
self::$memoized_updates = WPMUDEV_Dashboard::$api->calculate_upgrades( self::$memoized_local_projects );
}
}
if ( is_array( self::$memoized_updates ) && count( self::$memoized_updates ) ) {
// Loop all available WPMUDEV updates and merge them into WP updates.
foreach ( self::$memoized_updates as $id => $theme ) {
if ( 'theme' !== $theme['type'] ) {
continue;
}
if ( 1 !== (int) $theme['autoupdate'] ) {
continue;
}
$theme_slug = dirname( $theme['filename'] );
// Build theme listing.
$object = array();
$object['pid'] = $id; // we add this so we can detect it later when wp core autoupdater triggers.
$object['theme'] = $theme_slug;
$object['new_version'] = $theme['new_version'];
$object['url'] = $theme['url'] ?? '';
$object['package'] = WPMUDEV_Dashboard::$api->rest_url_auth( 'download/' . $id );
$object['tested'] = $cur_wp_version;
self::$memoized_theme_updates[ $theme_slug ] = $object;
}
}
}
// Finally merge available WPMUDEV updates into default WP update data.
if ( ! empty( self::$memoized_theme_updates ) ) {
if ( isset( $value->response ) && is_array( $value->response ) ) {
$value->response = array_merge( $value->response, self::$memoized_theme_updates );
} else {
$value->response = self::$memoized_theme_updates;
}
}
return $value;
}
/**
* Prints our custom async js tracking code in the site footer.
*
* @since 4.6
*/
public function analytics_tracking_code() {
/**
* Filter to enable/disable analytics tracking.
*
* @param bool $can_track Can be tracked.
*/
$can_track = apply_filters( 'wpmudev_dashboard_analytics_tracking', true );
// Early checks before continue.
if ( ! $can_track || ! WPMUDEV_Dashboard::$api->has_key() ) {
return;
}
$analytics_enabled = WPMUDEV_Dashboard::$settings->get( 'enabled', 'analytics' );
$analytics_site_id = WPMUDEV_Dashboard::$settings->get( 'site_id', 'analytics' );
$analytics_tracker = WPMUDEV_Dashboard::$settings->get( 'tracker', 'analytics' );
$analytics_script_url = WPMUDEV_Dashboard::$settings->get( 'script_url', 'analytics' );
$analytics_allowed = WPMUDEV_Dashboard::$api->is_analytics_allowed();
// define take priority.
$analytics_js_url = defined( 'WPMUDEV_ANALYTICS_JS_URL' ) ? WPMUDEV_ANALYTICS_JS_URL : $analytics_script_url;
if ( empty( $analytics_js_url ) ) {
// default if all empty.
$analytics_js_url = 'https://analytics.wpmucdn.com/matomo.js';
}
$analytics_js_url = esc_url_raw( $analytics_js_url );
// Check if analytics can be used.
if ( $analytics_allowed && $analytics_enabled && $analytics_site_id && $analytics_tracker ) {
// phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:disable Universal.WhiteSpace.PrecisionAlignment.Found
// phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact
// phpcs:disable Generic.WhiteSpace.ScopeIndent.Incorrect
?>
<script type="text/javascript">
var _paq = _paq || [];
<?php
if ( is_multisite() ) {
// This lets us use page titles view to filter basic results for a subsite based on domain (works with domain mapping too).
echo '_paq.push(["setDocumentTitle", "' . get_current_blog_id() . '/" + document.title]);' . PHP_EOL;
if ( is_subdomain_install() ) { // makes sure visitors are tracked across multisite (except domain mapped).
echo '_paq.push(["setCookieDomain", "*.' . esc_js( wp_parse_url( network_home_url(), PHP_URL_HOST ) ) . '"]);' . PHP_EOL;
echo '_paq.push(["setDomains", "*.' . esc_js( wp_parse_url( network_home_url(), PHP_URL_HOST ) ) . '"]);' . PHP_EOL;
}
}
// collect author stats on single post views, excluding pages.
if ( is_single() ) {
echo '_paq.push([\'setCustomDimension\', 1, \'{"ID":' . esc_js( get_the_author_meta( 'ID' ) ) . ',"name":"' . esc_js( get_the_author_meta( 'display_name' ) ) . '","avatar":"'
. esc_js( md5( get_the_author_meta( 'user_email' ) ) ) . '"}\']);' . PHP_EOL;
}
?>
_paq.push(['trackPageView']);
(function () {
var u = "<?php echo esc_js( trailingslashit( $analytics_tracker ) ); ?>";
_paq.push(['setTrackerUrl', u + 'track/']);
_paq.push(['setSiteId', '<?php echo intval( $analytics_site_id ); ?>']);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.type = 'text/javascript';
g.async = true;
g.defer = true;
g.src = '<?php echo esc_js( $analytics_js_url ); ?>';
s.parentNode.insertBefore(g, s);
})();
</script>
<?php
// phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:enable Universal.WhiteSpace.PrecisionAlignment.Found
// phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact
// phpcs:enable Generic.WhiteSpace.ScopeIndent.Incorrect
}
}
/**
* Can current blog user access the analytics widget.
*
* Translates the minimum role as defined in settings into a level capability, then checks if current user
* has that capability.
*
* @return bool
*/
public function user_can_analytics() {
$cap = 'level_10'; // default to administrator.
$role = get_role( WPMUDEV_Dashboard::$settings->get( 'role', 'analytics' ) );
if ( is_object( $role ) ) {
for ( $i = 0; $i <= 12; $i++ ) { // admin is 10, but we'll go a little past it just in case!
if ( isset( $role->capabilities[ 'level_' . $i ] ) && $role->capabilities[ 'level_' . $i ] ) {
$cap = 'level_' . $i;
}
}
}
$user_can = current_user_can( $cap );
/**
* Override whether user can view analytics stats
*
* @since 5.0.0
*/
return apply_filters( 'wpmudev_dashboard_user_can_view_analytics_stats', $user_can );
}
/**
* Exclude branding logo image from media library.
*
* If current user do not have access to WPMUDEV Dash settings,
* hide branding logo from media library.
*
* @param array $query WP_Query.
*
* @return array
*/
public function user_can_edit_branding_image( $query ) {
$user = get_current_user_id();
// Only if allowed user.
if ( is_admin() && ! $this->allowed_user( $user ) ) {
$query['post__not_in'] = array( WPMUDEV_Dashboard::$settings->get( 'branding_image_id', 'whitelabel' ) );
}
return $query;
}
/**
* Get Metrics displayed on analytics widget
*
* @since 4.7
* @return array
*/
public function get_metrics_on_analytics() {
$metrics = WPMUDEV_Dashboard::$settings->get( 'metrics', 'analytics' );
// default to all displayed.
if ( false === $metrics ) {
$metrics = array(
'pageviews',
'unique_pageviews',
'page_time',
'bounce_rate',
'exit_rate',
'visits',
);
}
return $metrics;
}
/**
* Get whitelabel settings as array assoc
*
* This function included default structure for whitelabel settings
* Static call allowed as long `WPMUDEV_Dashboard::$site` initialized
*
* @since 4.5.3
*
* @param array $structure Optional array assoc with expectation use when override needed only.
*
* @return array
* @deprecated 4.11.2 Use WPMUDEV_Dashboard::$whitelabel->get_settings().
*/
public function get_whitelabel_settings( $structure = array() ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
return WPMUDEV_Dashboard::$whitelabel->get_settings( $structure );
}
/**
* Get WPMUDEV branding that should be used.
*
* @since 4.6
*
* @param mixed $default_branding Default data.
* @param string $type (`all`, `hide_branding`, `hero_image`, `change_footer`, `footer_text`, `hide_doc_link`).
*
* @return array
* @deprecated 4.11.2 Use WPMUDEV_Dashboard_Whitelabel::get_branding().
*/
public function get_wpmudev_branding( $default_branding, $type = 'all' ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
if ( ! empty( $type ) ) {
_deprecated_argument( __METHOD__, '4.11.2' );
}
return WPMUDEV_Dashboard::$whitelabel->get_branding( $default_branding );
}
/**
* Get hide branding flag.
*
* @since 4.6
*
* @param bool $hide_branding Should hide branding.
*
* @return bool
* @deprecated 4.11.2 Use WPMUDEV_Dashboard_Whitelabel::get_hide_branding().
*/
public function get_wpmudev_branding_hide_branding( $hide_branding ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
return WPMUDEV_Dashboard::$whitelabel->get_hide_branding( $hide_branding );
}
/**
* Get Hero Image for branding
*
* @since 4.6
*
* @param string $hero_image Hero image link.
*
* @return string
* @deprecated 4.11.2 Use WPMUDEV_Dashboard_Whitelabel::get_branding_hero_image().
*/
public function get_wpmudev_branding_hero_image( $hero_image ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
return WPMUDEV_Dashboard::$whitelabel->get_branding_hero_image( $hero_image );
}
/**
* Get Footer Text for branding
*
* @since 4.6
*
* @param bool $change_footer Change footer?.
*
* @return bool
* @deprecated 4.11.2 Use WPMUDEV_Dashboard_Whitelabel::get_branding_change_footer().
*/
public function get_wpmudev_branding_change_footer( $change_footer ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
return WPMUDEV_Dashboard::$whitelabel->get_branding_change_footer( $change_footer );
}
/**
* Get Footer Text for branding
*
* @since 4.6
*
* @param string $footer_text Footer text.
*
* @return string
* @deprecated 4.11.2 Use WPMUDEV_Dashboard_Whitelabel::get_branding_footer_text().
*/
public function get_wpmudev_branding_footer_text( $footer_text ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
return WPMUDEV_Dashboard::$whitelabel->get_branding_footer_text( $footer_text );
}
/**
* Get Footer Text for branding
*
* @since 4.6
*
* @param bool $hide_doc_link Hide doc link?.
*
* @return bool
* @deprecated 4.11.2 Use WPMUDEV_Dashboard_Whitelabel::get_branding_hide_doc_link().
*/
public function get_wpmudev_branding_hide_doc_link( $hide_doc_link ) {
// Deprecated and moved to new class.
// Not using _deprecated_function() for now to avoid warnings.
return WPMUDEV_Dashboard::$whitelabel->get_branding_hide_doc_link( $hide_doc_link );
}
/**
* This function is where main logic of whitelabel-ing executed across plugins
*
* Couple hooks can be utilized by other plugin itself, to ease process of whitelabel-ing
*
* @since 4.6
*
* @param string $hook_suffix Hook suffix.
*
* @return bool
*/
public function whitelabel_plugin_admin_pages( $hook_suffix ) {
if ( ! isset( $hook_suffix ) || empty( $hook_suffix ) ) {
return false;
}
$whitelabel_settings = WPMUDEV_Dashboard::$whitelabel->get_settings();
$membership_type = WPMUDEV_Dashboard::$api->get_membership_status();
// activated.
if ( ! $whitelabel_settings['enabled'] || ( 'full' !== $membership_type && 'unit' !== $membership_type ) ) {
return false;
}
// base page of current screen map-ed to callable function(s).
$plugin_pages = array(
/**
* Hummingbird
*/
// Hummingbird MultiSite/SingleSite dashboard.
'toplevel_page_wphb' => array(
'wpmudev_whitelabel_sui_plugins_branding',
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// Hummingbird MultiSite subsite dashboard.
'toplevel_page_wphb-performance' => array(
'wpmudev_whitelabel_sui_plugins_branding',
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// performance page.
'hummingbird-pro_page_wphb-performance' => array(
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// caching page.
'hummingbird-pro_page_wphb-caching' => array(
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// gzip page.
'hummingbird-pro_page_wphb-gzip' => array(
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// advanced.
'hummingbird-pro_page_wphb-advanced' => array(
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// uptime.
'hummingbird-pro_page_wphb-uptime' => array(
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
// minification / Asset Optimization.
'hummingbird-pro_page_wphb-minification' => array(
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
/**
* Smush
*/
// Smush dashboard.
'toplevel_page_smush' => array(
'wpmudev_whitelabel_sui_plugins_branding',
'wpmudev_whitelabel_sui_plugins_footer',
'wpmudev_whitelabel_sui_plugins_doc_links',
),
);
/**
* Filter Plugin Pages Map to be whitelabel-ed
*
* This should return array, with `key` WP_Screen.base,
* and `value` is array with `callable`, it's queue, first registered, first executed
*
* Callable function should accept array as parameter,
* Within this array there will be `page_base`, `settings`.
*
* Callable is executed within `admin_head-$hook_suffix`
* with `999` as priority, which expected to be last-executed
*
* If callable are multiple then it works as queue,
* First registered, First executed,
* means `$callabels[0]` will called first
*
* If need to attach into another hook example `admin_print_footer_scripts`
* then attach it inside its callable function
*
* Using this filter encouraged, to avoid race condition,
* in case plugin hooks is loaded first before Dash plugin initiated,
* which will be needed as `WPMUDEV_Dashboard::$whitelabel->get_settings();` must be initiated
*
* @since 4.6
*
* @param array $plugin_pages
*/
$plugin_pages = apply_filters( 'wpmudev_whitelabel_plugin_pages', $plugin_pages );
$admin_print_footer_scripts_priority = 999;
// target configured pages.
if ( in_array( $hook_suffix, array_keys( $plugin_pages ), true ) ) {
$callables = $plugin_pages[ $hook_suffix ];
foreach ( $callables as $callable ) {
add_action( "admin_head-{$hook_suffix}", $callable, $admin_print_footer_scripts_priority );
}
return true;
}
return false;
}
/**
* Replace Free version plugin with Pro version if possible
*
* Since 4.7
*
* @since 5.0.0 Pass $err detail by reference
*
* @param int $project_id Project ID.
* @param null $doing_ajax Force enable/disable ajax-ify response, if `null` it will check `DOING_AJAX` constant.
* @param array|null $err Error details. Passed by reference.
*
* @return bool
*/
public function maybe_replace_free_with_pro( $project_id, $doing_ajax = null, ?array &$err = null ) {
$err = null; // reset error.
if ( null === $doing_ajax ) {
$doing_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
}
$pid = (int) $project_id;
$project_info = $this->get_project_info( $pid );
if ( empty( $project_info ) || ! isset( $project_info->pid ) || $pid !== $project_info->pid ) {
if ( $doing_ajax ) {
$this->send_json_error( array( 'message' => __( 'Failed to find plugin id.', 'wpmudev' ) ) );
}
return false;
}
// Plugins with same folder name.
$special_plugins = array(
51 => 'beehive-analytics',
2097296 => 'forminator',
);
$free_filename = '';
$is_free_installed = false;
$is_pro_success_installed = false;
if ( ! empty( $project_info->free_version_slug ) ) {
$free_filename = $project_info->free_version_slug;
// check if its installed.
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$all_plugins = get_plugins();
foreach ( $all_plugins as $slug => $all_plugin ) {
$slug = plugin_basename( $slug );
$free_filename = plugin_basename( $free_filename );
if ( $slug === $free_filename ) {
$is_free_installed = true;
break;
}
}
}
// Handle special plugins.
if ( isset( $special_plugins[ $pid ] ) && $is_free_installed ) {
// Move free free to another directory.
WPMUDEV_Dashboard::$utils->rename_plugin( $special_plugins[ $pid ], $special_plugins[ $pid ] . '-free' );
}
$local = WPMUDEV_Dashboard::$site->get_cached_projects( $pid );
// means its not installed yet.
if ( empty( $local ) ) {
// INSTALL PRO Plugin.
$install_pro = WPMUDEV_Dashboard::$upgrader->install( $pid );
if ( ! $install_pro ) {
$err = WPMUDEV_Dashboard::$upgrader->get_error();
if ( $doing_ajax ) {
$this->send_json_error( $err );
}
// Undo free move.
if ( isset( $special_plugins[ $pid ] ) && $is_free_installed ) {
WPMUDEV_Dashboard::$utils->rename_plugin( $special_plugins[ $pid ] . '-free', $special_plugins[ $pid ] );
}
return false;
}
$is_pro_success_installed = true;
}
// first thing first, DEACTIVATE.
// save current state for next usage.
$orig_active_blog = is_plugin_active( $free_filename );
$orig_active_network = is_multisite() && is_plugin_active_for_network( $free_filename );
// some plugins has their own method of upgrading itself to PRO version
// free version can be already deleted/uninstalled
// but somehow free plugins has active status here, so force deactivate free plugin when needed.
if ( $orig_active_blog || $orig_active_network ) {
deactivate_plugins( $free_filename, true, $orig_active_network );
}
// clear local cache, because we need if fresh data.
$this->clear_local_file_cache();
$local = WPMUDEV_Dashboard::$site->get_cached_projects( $pid );
$pro_filename = $local['filename'] ?? false;
// Set with previous state if is not.
if ( $orig_active_blog || $orig_active_network ) {
if ( $pro_filename ) {
$active_blog = is_plugin_active( $pro_filename );
$active_network = is_multisite() && is_plugin_active_for_network( $pro_filename );
if ( $orig_active_blog && ! $active_blog ) {
$activated = activate_plugin( $pro_filename, false, false, true );
if ( is_wp_error( $activated ) ) {
if ( $is_free_installed ) {
// attempt restore.
activate_plugin( $free_filename, false, false, true );
}
if ( $doing_ajax ) {
$this->send_json_error( array( 'message' => $activated->get_error_message() ) );
}
return false;
}
}
if ( $orig_active_network && ! $active_network ) {
$activated = activate_plugin( $pro_filename, false, true, true );
if ( is_wp_error( $activated ) ) {
if ( $is_free_installed ) {
// attempt restore.
activate_plugin( $free_filename, false, true, true );
}
if ( $doing_ajax ) {
$this->send_json_error( array( 'message' => $activated->get_error_message() ) );
}
return false;
}
}
}
}
if ( $is_free_installed && $is_pro_success_installed ) {
// DELETE FREE Plugin.
$delete_free = true;
if ( isset( $special_plugins[ $pid ] ) ) {
$this->delete_plugin_directory( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $special_plugins[ $pid ] . '-free' );
} else {
$delete_free = WPMUDEV_Dashboard::$upgrader->delete_plugin( $free_filename, true );
}
if ( ! $delete_free ) {
$err = WPMUDEV_Dashboard::$upgrader->get_error();
if ( $doing_ajax ) {
$this->send_json_error( $err );
}
return false;
}
}
$this->clear_local_file_cache();
return true;
}
/**
* Replace Pro version plugin with free version if possible.
*
* Since 4.11.9
*
* @param int $project_id Project ID.
*
* @return bool
*/
public function maybe_replace_pro_with_free( $project_id ) {
// Get project id.
$pid = (int) $project_id;
// Get project info.
$project_info = $this->get_project_info( $pid );
// If not a valid project.
if ( empty( $project_info ) || ! isset( $project_info->pid ) || $pid !== $project_info->pid ) {
return false;
}
// If free slug is not found, do not continue.
if ( empty( $project_info->free_version_slug ) ) {
return false;
}
// Get local plugin data.
$local = WPMUDEV_Dashboard::$site->get_cached_projects( $project_id );
// Check if Pro version is already installed.
$pro_installed = ! empty( $local['filename'] );
$pro_active = false;
$pro_active_network = false;
$forminator_pid = 2097296;
// Check if Pro version is active if only it's installed.
if ( $pro_installed ) {
$pro_active = is_plugin_active( $local['filename'] );
$pro_active_network = is_multisite() && is_plugin_active_for_network( $local['filename'] );
}
// Make sure functions are ready.
if ( ! function_exists( 'plugins_api' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
}
// Get wp.org slug of the plugin.
$slug = explode( '/', $project_info->free_version_slug );
// Build plugin API data.
$api = plugins_api(
'plugin_information',
array(
'slug' => $slug[0],
'fields' => array(
'short_description' => false,
'sections' => false,
'requires' => false,
'rating' => false,
'ratings' => false,
'downloaded' => false,
'last_updated' => false,
'added' => false,
'tags' => false,
'compatibility' => false,
'homepage' => false,
'donate_link' => false,
),
)
);
// No download link found.
if ( empty( $api->download_link ) ) {
return false;
}
// Forminator needs to be handled first.
if ( $forminator_pid === $pid && $pro_installed ) {
if ( $pro_active || $pro_active_network ) {
// Deactivate Forminator Pro version now.
deactivate_plugins( $local['filename'], true, $pro_active_network );
}
// Move free forminator to another directory.
WPMUDEV_Dashboard::$utils->rename_plugin( 'forminator', 'forminator-pro' );
}
// Try installing free version now.
$upgrader = new Plugin_Upgrader();
$free_installed = $upgrader->install( $api->download_link );
// If free installation was success.
if ( true === $free_installed ) {
// Deactivate Pro version now.
if ( $forminator_pid !== $pid && ( $pro_active || $pro_active_network ) ) {
deactivate_plugins( $local['filename'], true, $pro_active_network );
}
// Activate free version.
$free_activated = activate_plugin(
$project_info->free_version_slug,
false,
$pro_active_network,
true
);
if ( is_wp_error( $free_activated ) ) {
// Remove newly installed free version.
if ( $forminator_pid === $pid ) {
// Delete Forminator free.
$this->delete_plugin_directory( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'forminator' );
// Restore Forminator Pro.
WPMUDEV_Dashboard::$utils->rename_plugin( 'forminator-pro', 'forminator' );
} else {
WPMUDEV_Dashboard::$upgrader->delete_plugin( $project_info->free_version_slug, true );
}
// Reactivate Pro version.
if ( $pro_active || $pro_active_network ) {
// attempt restore.
activate_plugin(
$local['filename'],
false,
$pro_active_network,
true
);
}
return false;
} elseif ( $forminator_pid === $pid ) {
// Remove renamed directory.
$this->delete_plugin_directory( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'forminator-pro' );
} else {
// Delete Pro version now.
WPMUDEV_Dashboard::$upgrader->delete_plugin( $pid, true );
}
} elseif ( $forminator_pid === $pid && $pro_installed ) {
// Restore Forminator Pro.
WPMUDEV_Dashboard::$utils->rename_plugin( 'forminator-pro', 'forminator' );
if ( $pro_active || $pro_active_network ) {
// attempt restore.
activate_plugin(
$local['filename'],
false,
$pro_active_network,
true
);
}
}
// Make sure to clear caches.
$this->clear_local_file_cache();
return true;
}
/**
* Get Free versions of DEV projects that installed on this site
*
* @since 4.7
* @since 5.0.0 Remove force fetch / refresh projects data. WDD-571.
* @return array
*/
public function get_installed_free_projects() {
$projects = WPMUDEV_Dashboard::$api->get_projects_data();
$projects = $projects['projects'] ?? array();
// Temporary fix for Branda and Beehive missing or having invalid free version slugs. todo: this should be set in DEV projects settings.
if ( isset( $projects[9135] ) && empty( $projects[9135]['free_version_slug'] ?? '' ) ) {
$projects[9135]['free_version_slug'] = 'branda-white-labeling/ultimate-branding.php';
}
if ( isset( $projects[51] ) && empty( $projects[51]['free_version_slug'] ?? '' ) ) {
$projects[51]['free_version_slug'] = 'beehive-analytics/beehive-analytics.php';
}
if ( isset( $projects[2097296] ) && empty( $projects[2097296]['free_version_slug'] ?? '' ) ) {
$projects[2097296]['free_version_slug'] = 'forminator/forminator.php';
}
if ( isset( $projects[1107020] ) && empty( $projects[1107020]['free_version_slug'] ?? '' ) ) {
$projects[1107020]['free_version_slug'] = 'wordpress-popup/popover.php';
}
// Temporary fix end.
$available_free_projects = array();
foreach ( $projects as $project_id => $project ) {
if ( ! empty( $project['free_version_slug'] ) ) {
$available_free_projects[ $project['free_version_slug'] ] = $project;
}
}
$installed_free_projects = array();
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$installed_plugins = get_plugins();
foreach ( $installed_plugins as $slug => $installed_plugin ) {
if ( in_array( $slug, array_keys( $available_free_projects ), true ) ) {
$plugins_root = WP_PLUGIN_DIR;
if ( empty( $plugins_root ) ) {
$plugins_root = ABSPATH . 'wp-content/plugins';
}
$file_data = get_file_data( $plugins_root . DIRECTORY_SEPARATOR . $slug, array( 'WDP_ID' => 'WDP ID' ) );
// Skip WDP ID. this key existence meeans PRO.
if ( ! empty( $file_data['WDP_ID'] ) ) {
continue;
}
$installed_free_project = $available_free_projects[ $slug ];
$installed_free_project['is_installed'] = true;
$installed_free_project['has_update'] = false; // for now, not showing update flags for free plugins
// use name from FREE version.
$installed_free_project['name'] = $installed_plugin['Name'];
// use version from FREE version.
$installed_free_project['version'] = $installed_plugin['Version'];
$installed_free_project['version_installed'] = $installed_plugin['Version'];
$installed_free_project['version_latest'] = $installed_plugin['Version']; // for now, not showing update flags for free plugins.
$installed_free_project['is_active'] = is_multisite() ? is_plugin_active_for_network( $slug ) : is_plugin_active( $slug );
$installed_free_project['url'] = array(
'config' => ( function () use ( $installed_free_project ) {
if ( is_multisite() ) {
if ( empty( $installed_free_project['ms_config_url'] ) ) {
// In case if the plugin doesn't have network settings but have blog settings.
if ( ! empty( $installed_free_project['wp_config_url'] ) ) {
return esc_url( admin_url( $installed_free_project['wp_config_url'] ) );
}
} else {
return esc_url( network_admin_url( $installed_free_project['ms_config_url'] ) );
}
} elseif ( ! empty( $installed_free_project['wp_config_url'] ) ) {
return esc_url( admin_url( $installed_free_project['wp_config_url'] ) );
}
return esc_url( admin_url( 'plugins.php' ) );
} )(),
);
$installed_free_projects[ $slug ] = $installed_free_project;
}
}
return $installed_free_projects;
}
/**
* Hooks into the login_message filter to show friendly error in the login screen, when SSO is disabled or user is logged out of Dashboard.
*
* @since 4.7.3
*
* @param string $message Message.
*
* @return string Modified log in message.
*/
public function show_sso_friendly_error( $message ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['wdp_sso_fail'] ) ) {
if ( 'sso_disabled' === $_GET['wdp_sso_fail'] ) {
$message = '<div id="login_error">Couldn\'t log in with The Hub SSO because SSO is disabled in the WPMU DEV Dashboard.</div>';
} elseif ( 'no_logged_in_dashboard_user' === $_GET['wdp_sso_fail'] ) {
$message = '<div id="login_error">Couldn\'t log in with The Hub SSO because you are not logged into the WPMU DEV Dashboard.</div>';
}
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
return $message;
}
/**
* Delete plugin directory recursively. Only use for removing free plugin directory if
* Pro and Free plugin directories are the same.
*
* @param string $path - plugin directory absolute path.
*/
private function delete_plugin_directory( $path ) {
$files = glob( $path . '/*' );
foreach ( $files as $file ) {
is_dir( $file ) ? $this->delete_plugin_directory( $file ) : wp_delete_file( $file );
}
rmdir( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
}
/**
* Reset internal memoizer
*
* @since 5.0.0
* @return void
*/
public static function reset_memoizer() {
self::$memoized_local_projects = null;
self::$memoized_updates = null;
self::$memoized_plugin_updates = null;
self::$memoized_translation_updates = null;
self::$memoized_theme_updates = null;
}
}
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed
/**
* Returns true if the current member is on a full membership-level.
*
* @since 4.0.0
* @return bool
*/
function is_wpmudev_member() {
$type = WPMUDEV_Dashboard::$api->get_membership_status();
return 'full' === $type;
}
/**
* Returns true if the current member is on a single membership-level.
* If project ID is specified then validation is: Single membership for that
* specific project? Otherwise the licensed project ID is returned (or false
* if the member is not on a single license)
*
* @since 4.0.0
*
* @param int $pid Optional. The project ID to validate.
*
* @return bool|int
*/
function is_wpmudev_single_member( $pid = false ) {
$type = WPMUDEV_Dashboard::$api->get_membership_status();
$licensed_project_id = WPMUDEV_Dashboard::$api->get_membership_projects();
if ( 'single' === $type ) {
if ( $pid ) {
return intval( $pid ) === (int) $licensed_project_id;
} else {
return $licensed_project_id;
}
}
return false;
}
/**
* Returns true if the current member has an active membership.
* Membership can be anything. We just check if it's active,
*
* @since 4.11
*
* @return boolean
*/
function is_wpmudev_active_member() {
// Get membership type.
$type = WPMUDEV_Dashboard::$api->get_membership_status();
return ! in_array( $type, array( 'free', 'expired', 'paused' ), true );
}
// this function(s) placed here as its not directly related with WPMUDev Site module.
if ( ! function_exists( 'wpmudev_whitelabel_sui_plugins_branding' ) ) {
/**
* Whitelabel-ing Dashboard Hero image WPMUDev plugins that using SharedUI (sui) as base
*
* Its ONLY for SharedUI that have similar markup
* This function will accept no args, since it should called on `admin_head-$hook_suffix`
* Try to utilize `WPMUDEV_Dashboard::` if its needed
*
* @since 4.6
*/
function wpmudev_whitelabel_sui_plugins_branding() {
$whitelabel_settings = WPMUDEV_Dashboard::$whitelabel->get_settings();
$output = '';
if ( $whitelabel_settings['branding_enabled'] ) {
$branding_type = $whitelabel_settings['branding_type'];
if ( is_multisite() && ! is_network_admin() && $whitelabel_settings['branding_enabled_subsite'] && 'custom' === $branding_type ) {
if ( has_custom_logo() ) {
$custom_logo_id = get_theme_mod( 'custom_logo' );
$image = wp_get_attachment_image_src( $custom_logo_id, 'full' );
$image = $image[0];
}
} else {
$image = 'link' === $branding_type ? $whitelabel_settings['branding_image_link'] : $whitelabel_settings['branding_image'];
}
$additional_summary_class = ! empty( $image ) ? 'sui-rebranded' : 'sui-unbranded';
ob_start();
// phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:disable Universal.WhiteSpace.PrecisionAlignment.Found
?>
<style>
#wpbody-content .sui-wrap div.sui-box.sui-summary {
background-image: url(<?php echo esc_url( $image ); ?>);
}
<?php if ( empty( $image ) ) : ?>
#wpbody-content .sui-wrap div.sui-box.sui-summary .sui-summary-image-space {
display: none;
}
#wpbody-content .sui-wrap div.sui-box.sui-summary .sui-summary-segment {
width: calc(100% / 2 - 2px);
}
#wpbody-content .sui-wrap div.sui-box.sui-summary > div:nth-child(2).sui-summary-segment {
padding-left: 0;
}
@media (max-width: 600px) {
#wpbody-content .sui-wrap div.sui-box.sui-summary .sui-summary-segment {
width: 100%;
}
}
<?php else : ?>
#wpbody-content .sui-wrap div.sui-box.sui-summary {
background-position: 3% 50%;
}
@media (max-width: 782px) {
#wpbody-content .sui-wrap div.sui-box.sui-summary {
background-image: none;
}
}
<?php endif; ?>
</style>
<script type="text/javascript">
if (typeof window.jQuery !== 'undefined') {
jQuery(document).ready(function () {
var wpmudev_whitelabel_summary_class = function () {
var sui_summary = jQuery('#wpbody-content .sui-wrap div.sui-box.sui-summary');
sui_summary.addClass('<?php echo esc_html( $additional_summary_class ); ?>');
}();
});
}
</script>
<?php
// phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:enable Universal.WhiteSpace.PrecisionAlignment.Found
$output = ob_get_clean();
}
/**
* Filter output SUI whitelabel-ing
*
* @since 4.6
*/
$output = apply_filters( 'wpmudev_whitelabel_sui_plugins_branding', $output );
// escaped above.
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
if ( ! function_exists( 'wpmudev_whitelabel_sui_plugins_footer' ) ) {
/**
* Whitelabel-ing Footer WPMUDev plugins that using SharedUI (sui) as base
*
* Its ONLY for SharedUI that have similar markup
* This function will accept no args, since it should called on `admin_head-$hook_suffix`
* Try to utilize `WPMUDEV_Dashboard::` if its needed
*
* @since 4.6
*/
function wpmudev_whitelabel_sui_plugins_footer() {
$whitelabel_settings = WPMUDEV_Dashboard::$whitelabel->get_settings();
$output = '';
if ( $whitelabel_settings['footer_enabled'] ) {
$text = $whitelabel_settings['footer_text'];
$text = wp_kses_post( $text ); // sanitize / escape.
ob_start();
// phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:disable Universal.WhiteSpace.PrecisionAlignment.Found
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<style>
#wpbody-content .sui-footer {
visibility: hidden;
}
#wpbody-content .sui-footer-nav {
display: none;
}
#wpbody-content .sui-footer-social {
display: none;
}
</style>
<script type="text/javascript">
if (typeof window.jQuery !== 'undefined') {
jQuery(document).ready(function () {
var wpmudev_whitelabel_footer = function () {
var sui_footer = jQuery('#wpbody-content .sui-footer');
sui_footer.html('<?php echo $text; ?>');
sui_footer.css('visibility', 'visible');
}();
});
}
</script>
<?php
// phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
$output = ob_get_clean();
}
/**
* Filter output SUI whitelabel-ing
*
* @since 4.6
*/
$output = apply_filters( 'wpmudev_whitelabel_sui_plugins_footer', $output );
// escaped above.
echo $output; // phpcs:ignore
}
}
if ( ! function_exists( 'wpmudev_whitelabel_sui_plugins_docs' ) ) {
/**
* Whitelabel-ing Docs buttons WPMUDev plugins that using SharedUI (sui) as base
*
* Its ONLY for SharedUI that have similar markup
* This function will accept no args, since it should called on `admin_head-$hook_suffix`
* Try to utilize `WPMUDEV_Dashboard::` if its needed
*
* @since 4.6
*/
function wpmudev_whitelabel_sui_plugins_doc_links() {
$whitelabel_settings = WPMUDEV_Dashboard::$whitelabel->get_settings();
$output = '';
if ( $whitelabel_settings['doc_links_enabled'] ) {
ob_start();
// this one need to be done via javascript
// because some page have another extra button like `New Test` \ `Recheck-images`.
// phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
?>
<style>
#wpbody-content .sui-wrap .sui-header .sui-actions-right {
visibility: hidden;;
}
</style>
<script type="text/javascript">
if (typeof window.jQuery !== 'undefined') {
jQuery(document).ready(function () {
var wpmudev_whitelabel_docs = function () {
var sui_action_right = jQuery('#wpbody-content .sui-wrap .sui-header .sui-actions-right');
var header_right_action_links = sui_action_right.find('a');
if (header_right_action_links.length) {
header_right_action_links.each(function () {
var link = jQuery(this),
href = link.attr('href');
// remove docs.*
if (/premium\.wpmudev\.org\/(docs|project)\/.*/i.test(href)) {
link.remove();
}
});
}
sui_action_right.css('visibility', 'visible');
}();
});
}
</script>
<?php
// phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
$output = ob_get_clean();
}
/**
* Filter output SUI whitelabel-ing
*
* @since 4.6
*/
$output = apply_filters( 'wpmudev_whitelabel_sui_plugins_doc_links', $output );
// no escape required.
echo $output; // phpcs:ignore
}
}
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed