Files

603 lines
16 KiB
PHP

<?php
/**
* Class: View manager.
*
* Helper class to show the views in dashboard area.
*
* @package wsal
*/
declare(strict_types=1);
namespace WSAL\Helpers;
use WpSecurityAuditLog;
use WSAL\Controllers\Alert;
use WSAL\Views\Setup_Wizard;
use WSAL\Helpers\Settings_Helper;
use WSAL\Controllers\Alert_Manager;
use WSAL\ListAdminEvents\List_Events;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\WSAL\Helpers\View_Manager' ) ) {
/**
* View Manager.
*
* This class includes all the views, initialize them and shows the active one.
* It creates also the menu items.
*
* @package wsal
*/
class View_Manager {
/**
* Array of views.
*
* @var WSAL_AbstractView[]
*/
public static $views = array();
/**
* Active view.
*
* @var \WSAL_AbstractView|null
*/
protected static $active_view = false;
/**
* Method: Constructor.
*
* @since 1.0.0
*/
public static function init() {
// Skipped views array.
$skip_views = array();
$views = array(
'WSAL_Views_AuditLog',
'WSAL_Views_Help',
'WSAL_Views_Settings',
'WSAL_Views_ToggleAlerts',
'\WSAL\Views\Premium_Features',
);
/**
* Skipped Views.
*
* Array of views which are skipped before plugin views are initialized.
*
* As of version 4.5 this no longer holds a list with files, but the name of the classes (namespaces must be included if they are in use)
*
* @param array $skip_views - Skipped views.
*
* @since 4.5.0
*/
$skip_views = apply_filters( 'wsal_skip_views', $skip_views );
$views_to_load = array_diff( $views, $skip_views );
foreach ( $views_to_load as $view ) {
if ( is_subclass_of( $view, '\WSAL_AbstractView' ) ) {
global $wsal_class;
if ( null === $wsal_class ) {
$wsal_class = WpSecurityAuditLog::get_instance();
}
self::$views[] = new $view( $wsal_class );
}
}
// Stop Freemius from hiding the menu on sub sites under certain circumstances.
add_filter(
'fs_should_hide_site_admin_settings_on_network_activation_mode_wp-security-audit-log',
array(
__CLASS__,
'bypass_freemius_menu_hiding',
)
);
// Add menus.
add_action( 'admin_menu', array( __CLASS__, 'add_admin_menus' ) );
add_action( 'network_admin_menu', array( __CLASS__, 'add_admin_menus' ) );
// Add plugin shortcut links.
add_filter( 'plugin_action_links_' . WSAL_BASE_NAME, array( __CLASS__, 'add_plugin_shortcuts' ) );
// Register shared scripts early so extension callbacks at priority 10 can use them as dependencies.
add_action( 'admin_enqueue_scripts', array( 'WSAL_Views_AuditLog', 'register_scripts' ), 9 );
// Render header.
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'render_view_header' ) );
// Render footer.
add_action( 'admin_footer', array( __CLASS__, 'render_view_footer' ) );
// Initialize setup wizard.
if ( ! Settings_Helper::get_boolean_option_value( 'setup-complete', false )
&& Settings_Helper::current_user_can( 'edit' )
) {
Setup_Wizard::init();
}
add_action( 'admin_head', array( __CLASS__, 'hide_freemius_sites_section' ) );
\add_action( 'admin_head', array( __CLASS__, 'print_menu_badge_style' ) );
// Check if WFCM is running by seeing if we have the version defined.
if ( defined( 'WFCM_VERSION' ) && ( version_compare( \WFCM_VERSION, '1.6.0', '<' ) ) ) {
add_action( 'admin_notices', array( __CLASS__, 'update_wfcm_notice' ) );
add_action( 'network_admin_notices', array( __CLASS__, 'update_wfcm_notice' ) );
}
}
/**
* Display notice if user is using older version of WFCM
*
* @since 5.0.0
*/
public static function update_wfcm_notice() {
if ( defined( 'WFCM_VERSION' ) ) {
if ( version_compare( \WFCM_VERSION, '1.6.0', '<' ) ) {
echo '<div class="notice notice-success">
<p>' . esc_html__( 'WP Activity Log requires Website File Changes Monitor 1.6.0. Please upgrade that plugin.', 'wp-security-audit-log' ) . '</p>
</div>';
}
}
}
/**
* Add new view given class name.
*
* @param string $class Class name.
*
* @since 5.0.0
*/
public static function add_from_class( $class ) {
if ( is_subclass_of( $class, '\WSAL_AbstractView' ) ) {
global $wsal_class;
self::$views[] = new $class( $wsal_class );
} else {
self::$views[] = $class;
}
}
/**
* Order views by their declared weight.
*
* @since 5.0.0
*/
public static function reorder_views() {
usort( self::$views, array( __CLASS__, 'order_by_weight' ) );
}
/**
* Get page order by its weight.
*
* @internal This has to be public for PHP to call it.
* @param \WSAL_AbstractView $a - First view.
* @param \WSAL_AbstractView $b - Second view.
*
* @return int
*
* @since 5.0.0
*/
public static function order_by_weight( $a, $b ) {
$wa = \call_user_func( array( $a, 'get_weight' ) ); // $a->get_weight();
$wb = \call_user_func( array( $b, 'get_weight' ) ); // $b->get_weight();
switch ( true ) {
case $wa < $wb:
return -1;
case $wa > $wb:
return 1;
default:
return 0;
}
}
/**
* WordPress Action
*
* @since 5.0.0
*/
public static function add_admin_menus() {
self::reorder_views();
if ( Settings_Helper::current_user_can( 'view' ) && count( self::$views ) ) {
// Add main menu.
$main_view_menu_slug = \call_user_func( array( self::$views[0], 'get_safe_view_name' ) );
\call_user_func(
array( self::$views[0], 'set_hook_suffix' ),
add_menu_page(
'WP Activity Log',
'WP Activity Log' . self::get_updates_count_html(),
'read', // No capability requirement.
$main_view_menu_slug,
array( __CLASS__, 'render_view_body' ),
self::$views[0]->get_icon(),
'2.5' // Right after dashboard.
)
);
List_Events::add_screen_options( \call_user_func( array( self::$views[0], 'get_hook_suffix' ) ) );
// Protected views to be displayed only to user with full plugin access.
$protected_views = array(
'wsal-togglealerts',
'wsal-usersessions-views',
'wsal-settings',
'wsal-ext-settings',
'wsal-rep-views-main',
'wsal-np-notifications',
'wsal-setup',
'wsal-reports-new',
'wsal-notifications',
);
// Check edit privileges of the current user.
$has_current_user_edit_priv = Settings_Helper::current_user_can( 'edit' );
// Add menu items.
foreach ( self::$views as $view ) {
if ( \call_user_func( array( $view, 'is_accessible' ) ) ) {
$safe_view_name = \call_user_func( array( $view, 'get_safe_view_name' ) );
if ( self::get_class_name_by_view( $safe_view_name ) ) {
continue;
}
if ( in_array( $safe_view_name, $protected_views, true ) && ! $has_current_user_edit_priv ) {
continue;
}
\call_user_func(
array( $view, 'set_hook_suffix' ),
add_submenu_page(
\call_user_func( array( $view, 'is_visible' ) ) ? $main_view_menu_slug : null,
\call_user_func( array( $view, 'get_title' ) ),
\call_user_func( array( $view, 'get_name' ) ),
'read', // No capability requirement.
$safe_view_name,
array( __CLASS__, 'render_view_body' )
)
);
}
}
// @free:start
add_submenu_page(
'wsal-auditlog',
'Upgrade',
'<span class="fs-submenu-item wp-security-audit-log pricing upgrade-mode" style="color:#FF8977;">Upgrade to Premium</span>',
'read', // No capability requirement.
'upgrade',
array(),
301
);
// @free:end
}
}
/**
* Returns the badge HTML appended to the WP Activity Log top-level menu title.
*
* @return string
*
* @since 5.2.2
*/
public static function get_updates_count_html(): string {
$count = Notices::get_number_of_notices();
if ( 0 >= $count ) {
return '';
}
return sprintf(
' <span id="wsal-notices-menu" class="update-plugins"><span class="update-count">%s</span></span>',
\esc_html( \number_format_i18n( $count ) )
);
}
/**
* Prints the inline <style> block that styles the menu badge.
*
* @return void
*
* @since 6.0.0
*/
public static function print_menu_badge_style() {
if ( 0 >= Notices::get_number_of_notices() ) {
return;
}
echo '<style>
#wsal-notices-menu .update-count {
position: absolute;
top: 2px;
right: 6px;
min-width: 20px;
line-height: 20px;
background: #d63638;
border-radius: 50%;
font-weight: bold;
}
#adminmenu #wsal-notices-menu.update-plugins {
display: inline;
background: none;
}
</style>';
}
/**
* WordPress Filter
*
* @param array $old_links - Array of old links.
*
* @since 5.0.0
*/
public static function add_plugin_shortcuts( $old_links ) {
self::reorder_views();
$new_links = array();
foreach ( self::$views as $view ) {
if ( \call_user_func( array( $view, 'has_plugin_shortcut_link' ) ) ) {
$new_links[] = '<a href="' . add_query_arg( 'page', \call_user_func( array( $view, 'get_safe_view_name' ) ), \network_admin_url( 'admin.php' ) ) . '">' . \call_user_func( array( $view, 'get_name' ) ) . '</a>';
if ( 1 === count( $new_links ) && ! wsal_freemius()->is__premium_only() ) {
// Trial link.
$trial_link = 'https://melapress.com/wordpress-activity-log/pricing/?utm_source=plugin&utm_medium=link&utm_campaign=wsal';
$new_links[] = '<a style="font-weight:bold; color:#049443 !important" href="' . $trial_link . '" target="_blank">' . __( 'Get Premium!', 'wp-security-audit-log' ) . '</a>';
}
}
}
return array_merge( $new_links, $old_links );
}
/**
* Returns the current active view or null if none.
*
* @return \WSAL_AbstractView|null
*
* @since 5.0.0
*/
public static function get_active_view() {
if ( false === self::$active_view ) {
self::$active_view = null;
// Get current view via $_GET array.
$current_view = ( isset( $_GET['page'] ) ) ? \sanitize_text_field( \wp_unslash( $_GET['page'] ) ) : '';
if ( isset( $current_view ) ) {
foreach ( self::$views as $view ) {
if ( \call_user_func( array( $view, 'get_safe_view_name' ) ) === $current_view ) {
self::$active_view = $view;
}
}
}
}
return self::$active_view;
}
/**
* Render header of the current view.
*
* @since 5.0.0
*/
public static function render_view_header() {
$view = self::get_active_view();
if ( $view ) {
\call_user_func( array( $view, 'header' ) );
}
}
/**
* Render footer of the current view.
*
* @since 5.0.0
*/
public static function render_view_footer() {
$view = self::get_active_view();
if ( $view ) {
\call_user_func( array( $view, 'footer' ) );
}
global $pagenow;
$is_auditlog_price_page = ( isset( $_GET['page'] ) && 'wsal-auditlog-pricing' === \sanitize_text_field( \wp_unslash( $_GET['page'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// No nonce needed, this only reads the standard WP admin routing parameter for a strict string comparison.
if ( 'admin.php' === $pagenow && $is_auditlog_price_page ) {
?>
<style>
.fs-full-size-wrapper {
margin: 0px 0 -65px -20px !important;
}
#root .fs-app-header .fs-page-title h2, #fs_pricing_wrapper .fs-app-header .fs-page-title h2 {
font-size: 23px;
font-weight: 400;
margin: 0;
padding: 9px 0 4px 20px;
line-height: 1.3;
}
@media only screen and (max-width: 768px) {
#root #fs_pricing_wrapper .fs-app-main .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu, #fs_pricing_wrapper #fs_pricing_wrapper .fs-app-main .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu {
padding: 5px;
display: flex;
width: 100%;
margin: 0 auto;
}
}
</style>
<?php
}
}
/**
* Render content of the current view.
*
* @since 5.0.0
*/
public static function render_view_body() {
$view = self::get_active_view();
if ( $view && $view instanceof \WSAL_AbstractView ) {
?>
<div class="wrap">
<?php
$view->render_icon();
$view->render_title();
$view->render_content();
?>
</div>
<?php
} elseif ( $view ) {
?>
<div class="wrap">
<?php
$view::render_icon();
$view::render_title();
$view::render_content();
?>
</div>
<?php
}
}
/**
* Returns view instance corresponding to its class name.
*
* @param string $class_name View class name.
*
* @return WSAL_AbstractView|bool The view or false on failure.
*
* @since 5.0.0
*/
public static function find_by_class_name( $class_name ) {
foreach ( self::$views as $view ) {
if ( $view instanceof $class_name ) {
return $view;
}
}
return false;
}
/**
* Method: Returns class name of the view using view name.
*
* @param string $view_slug - Slug of view.
* @since 1.0.0
*/
private static function get_class_name_by_view( $view_slug ) {
$not_show = false;
switch ( $view_slug ) {
case 'wsal-loginusers':
if ( class_exists( 'WSAL_UserSessions_Plugin' ) ) {
$not_show = true;
}
break;
case 'wsal-search':
if ( class_exists( 'WSAL_SearchExtension' ) ) {
$not_show = true;
}
break;
case 'wsal-externaldb':
if ( class_exists( 'WSAL_Ext_Plugin' ) ) {
$not_show = true;
}
break;
default:
break;
}
return $not_show;
}
/**
* Hide Freemius sites section on the account page
* of a multisite WordPress network.
*
* @since 5.0.0
*/
public static function hide_freemius_sites_section() {
global $pagenow;
// Return if not multisite.
if ( ! is_multisite() ) {
return;
}
// Return if multisite but not on the network admin.
if ( ! is_network_admin() ) {
return;
}
// Return if the pagenow is not on admin.php page.
if ( 'admin.php' !== $pagenow ) {
return;
}
// No nonce needed, this only reads the standard WP admin routing parameter for a strict string comparison.
$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( 'wsal-auditlog-account' === $page ) {
echo '<style type="text/css">#fs_sites {display:none;}</style>';
}
}
/**
* Bypasses Freemius hiding menu items.
*
* @param bool $should_hide Should allow Freemium to hide menu items.
*
* @return bool
*
* @since 5.0.0
*/
public static function bypass_freemius_menu_hiding( $should_hide ) {
return false;
}
/**
* Builds a relative asset path that takes SCRIPT_DEBUG constant into account.
*
* @param string $path Path relative to the plugin folder.
* @param string $filename Filename base (.min is optionally appended to this).
* @param string $extension File extension.
* @param bool $use_minified_version If true, the minified version of the file is used.
*
* @return string
*
* @since 5.0.0
*/
public static function get_asset_path( $path, $filename, $extension, $use_minified_version = true ) {
$result = $path . $filename . '.';
if ( $use_minified_version && SCRIPT_DEBUG ) {
$result .= 'min.';
}
$result .= $extension;
return $result;
}
/**
* Returns all the registered views
*
* @return array
*
* @since 5.0.0
*/
public static function get_views(): array {
return self::$views;
}
}
}