*/
namespace blobfolio\wp\meow;
use blobfolio\wp\meow\vendor\common;
class admin {
const EXTENSIONS = array(
'date',
'filter',
'json',
'pcre',
);
protected static $errors = array();
protected static $version;
protected static $remote_version;
protected static $remote_home;
// ---------------------------------------------------------------------
// General
// ---------------------------------------------------------------------
/**
* Warnings
*
* @return bool True/false.
*/
public static function warnings() {
global $pagenow;
// Only show warnings to administrators, and only on relevant pages.
if (
! \current_user_can('manage_options') ||
('plugins.php' !== $pagenow && false === static::current_screen())
) {
return true;
}
if (options::get('license') && ! options::is_pro()) {
static::$errors[] = \__('The Apocalypse Meow license is not valid for this domain or plugin; premium features have been disabled.', 'apocalypse-meow');
}
elseif (options::is_pro() && ! \extension_loaded('openssl')) {
static::$errors[] = \__('The recommended PHP extension OpenSSL is missing; this will slow down some operations.', 'apocalypse-meow');
}
// Only warn about Intl if it appears to be needed.
if (
\function_exists('mb_check_encoding') &&
! \function_exists('idn_to_ascii') &&
(! \mb_check_encoding(\site_url(), 'ASCII') || (false !== \strpos(\site_url(), 'xn--')))
) {
static::$errors[] = \__('The recommended PHP extension Intl is missing; you will not be able to handle internationalized or unicode domains.', 'apocalypse-meow');
}
// All good!
if (! \count(static::$errors)) {
return true;
}
?>
Apocalypse Meow'
);
?>
• • ', static::$errors); ?>
id) &&
static::has_update()
) {
echo '' . \sprintf(
\__('%s %s has been released! Must-Use plugins must be updated manually, so click %s to download the new version.', 'apocalypse-meow'),
'Apocalypse Meow',
'' . static::$remote_version . '',
'' . \__('here', 'apocalypse-meow') . ''
) . '
';
}
}
}
/**
* Has Update?
*
* We need to query the API because WordPress won't check for
* updates if this plugin is installed as Must-Use.
*
* @return bool True/false.
*/
public static function has_update() {
return (\version_compare(static::get_version(), static::get_remote_version()) < 0);
}
/**
* Get Plugin Version
*
* @return string Version.
*/
public static function get_version() {
if (\is_null(static::$version)) {
$plugin_data = \get_plugin_data(\MEOW_INDEX, false, false);
if (isset($plugin_data['Version'])) {
static::$version = $plugin_data['Version'];
}
else {
static::$version = '0.0';
}
}
return static::$version;
}
/**
* Get Remote Version
*
* @return string Version.
*/
public static function get_remote_version() {
if (\is_null(static::$remote_version)) {
require_once \trailingslashit(\ABSPATH) . 'wp-admin/includes/plugin.php';
require_once \trailingslashit(\ABSPATH) . 'wp-admin/includes/plugin-install.php';
$response = \plugins_api(
'plugin_information',
array('slug'=>'apocalypse-meow')
);
if (
! \is_wp_error($response) &&
\is_a($response, 'stdClass') &&
isset($response->version)
) {
static::$remote_version = $response->version;
static::$remote_home = $response->homepage;
}
else {
static::$remote_version = '0.0';
}
}
return static::$remote_version;
}
/**
* Localize
*
* @return void Nothing.
*/
public static function localize() {
if (\MEOW_MUST_USE) {
\load_muplugin_textdomain('apocalypse-meow', \basename(\MEOW_PLUGIN_DIR) . '/languages');
}
else {
\load_plugin_textdomain('apocalypse-meow', false, \basename(\MEOW_PLUGIN_DIR) . '/languages');
}
}
/**
* Current Screen
*
* The WP Current Screen function isn't ready soon enough
* for our needs, so we need to get creative.
*
* @return bool|string WH screen type or false.
*/
public static function current_screen() {
// Obviously this needs to be an admin page.
if (! \is_admin()) {
return false;
}
// Could be a miscellaneous page.
if (\array_key_exists('page', $_GET)) {
if (\preg_match('/^meow\-/', $_GET['page'])) {
return $_GET['page'];
}
}
return false;
}
/**
* Privacy Policy
*
* @return void Nothing.
*/
public static function privacy_policy() {
if (\function_exists('wp_add_privacy_policy_content')) {
// The default.
// phpcs:disable
$privacy = __("This site retains security logs of every log-in attempt made to the CMS backend. This information — including the end user's public IP address, username, and the status of his or her attempt — is used to help prevent unauthorized system access and maintain Quality of Service for all site visitors.", 'apocalypse-meow');
// phpcs:enable
// Community pool additionally shares this information with
// Blobfolio.
if (options::get('login-community')) {
$privacy .= "\n\n" . \sprintf(
// phpcs:disable
__('For additional security, this web site participates in a community-sourced attack traffic monitoring and mitigation program. As a contributing member, IP addresses associated with attacks against this web site are periodically shared with Blobfolio, LLC (%s), the maintainer of the centralized database. Any IP addresses identified by multiple, independent sources are published to a publicly available blocklist.', 'apocalypse-meow'),
// phpcs:enable
'' . \__('Privacy Policy', 'apocalypse-meow') . ''
);
}
// Mention pruning.
if (options::get('prune-active')) {
$privacy .= "\n\n" . \sprintf(
\__('This data is only retained while relevant for security purposes and is automatically removed after %d days.', 'apocalypse-meow'),
options::get('prune-limit')
);
}
else {
$privacy .= "\n\n" . \__('This information is exclusively used to help restrict unauthorized system access and maintain quality of service. Log-in data is not shared with any third-party.', 'apocalypse-meow');
}
// Add the notice!
\wp_add_privacy_policy_content(
'Apocalypse Meow',
\wp_kses_post(\wpautop($privacy))
);
}
}
/**
* Sister Plugins
*
* Get a list of other plugins by Blobfolio.
*
* @return array Plugins.
*/
public static function sister_plugins() {
require_once \trailingslashit(\ABSPATH) . 'wp-admin/includes/plugin.php';
require_once \trailingslashit(\ABSPATH) . 'wp-admin/includes/plugin-install.php';
$response = \plugins_api(
'query_plugins',
array(
'author'=>'blobfolio',
'per_page'=>20,
)
);
if (! isset($response->plugins) || ! \is_array($response->plugins)) {
return array();
}
// We want to know whether a plugin is on the system, not
// necessarily whether it is active.
$plugin_base = \trailingslashit(\WP_PLUGIN_DIR);
$mu_base = \defined('WPMU_PLUGIN_DIR') ? \trailingslashit(\WPMU_PLUGIN_DIR) : false;
$plugins = array();
foreach ($response->plugins as $p) {
// WordPress changed the formatting; let's make sure we
// always have an array.
if (! \is_array($p)) {
$p = (array) $p;
}
if (
! isset($p['slug']) ||
('apocalypse-meow' === $p['slug']) ||
\file_exists("{$plugin_base}{$p['slug']}") ||
($mu_base && \file_exists("{$mu_base}{$p['slug']}"))
) {
continue;
}
$plugins[] = array(
'name'=>$p['name'],
'slug'=>$p['slug'],
'description'=>$p['short_description'],
'url'=>$p['homepage'],
'version'=>$p['version'],
);
}
\usort(
$plugins,
function($a, $b) {
if ($a['name'] === $b['name']) {
return 0;
}
return $a['name'] > $b['name'] ? 1 : -1;
}
);
return $plugins;
}
// --------------------------------------------------------------------- end general
// ---------------------------------------------------------------------
// Menus & Pages
// ---------------------------------------------------------------------
/**
* Register Scripts & Styles
*
* Register our assets and enqueue some of them maybe.
*
* @return bool True/false.
*/
public static function enqueue_scripts() {
// Find our CSS and JS roots. Easy if this
// is a regular plugin.
$js = \MEOW_PLUGIN_URL . 'js/';
$css = \MEOW_PLUGIN_URL . 'css/';
// Dashboard CSS.
\wp_register_style(
'meow_css_dashboard',
"{$css}dashboard.css",
array(),
\MEOW_VERSION
);
\wp_enqueue_style('meow_css_dashboard');
// The rest is for our pages.
if (false === ($screen = static::current_screen())) {
return true;
}
// Chartist CSS.
\wp_register_style(
'meow_css_chartist',
"{$css}chartist.css",
array(),
\MEOW_VERSION
);
if ('meow-stats' === $screen) {
\wp_enqueue_style('meow_css_chartist');
}
// Prism CSS.
\wp_register_style(
'meow_css_prism',
"{$css}prism.css",
array(),
\MEOW_VERSION
);
if (\in_array($screen, array('meow-help', 'meow-settings', 'meow-tools'), true)) {
\wp_enqueue_style('meow_css_prism');
}
// Main CSS.
\wp_register_style(
'meow_css',
"{$css}core.css",
array(),
\MEOW_VERSION
);
\wp_enqueue_style('meow_css');
// Chartist JS.
\wp_register_script(
'meow_js_chartist',
"{$js}chartist.min.js",
array('meow_js_vue'),
\MEOW_VERSION,
true
);
// Clipboard JS.
\wp_register_script(
'meow_js_clipboard',
"{$js}clipboard.min.js",
array(),
\MEOW_VERSION,
true
);
// Prism JS.
\wp_register_script(
'meow_js_prism',
"{$js}prism.min.js",
array('meow_js_clipboard'),
\MEOW_VERSION,
true
);
// Vue JS.
\wp_register_script(
'meow_js_vue',
(\defined('WP_DEBUG') && \WP_DEBUG ? "{$js}vue-debug.min.js" : "{$js}vue.min.js"),
array('jquery'),
\MEOW_VERSION,
true
);
// Pro JS.
\wp_register_script(
'meow_js_pro',
"{$js}core-pro.min.js",
array(
'meow_js_vue',
),
\MEOW_VERSION,
true
);
if ('meow-pro' === $screen) {
\wp_enqueue_script('meow_js_pro');
}
// Activity JS.
\wp_register_script(
'meow_js_activity',
"{$js}core-activity.min.js",
array(
'meow_js_vue',
),
\MEOW_VERSION,
true
);
if ('meow-activity' === $screen) {
\wp_enqueue_script('meow_js_activity');
}
// Help JS.
\wp_register_script(
'meow_js_help',
"{$js}core-help.min.js",
array(
'meow_js_vue',
'meow_js_prism',
),
\MEOW_VERSION,
true
);
if ('meow-help' === $screen) {
\wp_enqueue_script('meow_js_help');
}
// Retroactive Reset JS.
\wp_register_script(
'meow_js_retroactive_reset',
"{$js}core-retroactive-reset.min.js",
array(
'meow_js_vue',
),
\MEOW_VERSION,
true
);
if ('meow-retroactive-reset' === $screen) {
\wp_enqueue_script('meow_js_retroactive_reset');
}
// Settings JS.
\wp_register_script(
'meow_js_settings',
"{$js}core-settings.min.js",
array(
'meow_js_vue',
'meow_js_prism',
),
\MEOW_VERSION,
true
);
if ('meow-settings' === $screen) {
\wp_enqueue_script('meow_js_settings');
}
// Stats JS.
\wp_register_script(
'meow_js_stats',
"{$js}core-stats.min.js",
array(
'meow_js_vue',
'meow_js_chartist',
),
\MEOW_VERSION,
true
);
if ('meow-stats' === $screen) {
\wp_enqueue_script('meow_js_stats');
}
// Tools JS.
\wp_register_script(
'meow_js_tools',
"{$js}core-tools.min.js",
array(
'meow_js_vue',
'meow_js_prism',
),
\MEOW_VERSION,
true
);
if ('meow-tools' === $screen) {
\wp_enqueue_script('meow_js_tools');
}
return true;
}
/**
* Register Menus
*
* @return void Nothing.
*/
public static function register_menus() {
$pages = array(
'retroactive_reset',
'settings',
'activity',
'stats',
'tools',
'help',
'pro',
'rename',
);
foreach ($pages as $page) {
\add_action('admin_menu', array(static::class, "{$page}_menu"));
}
// Register plugins page quick links if we aren't running in
// Must-Use mode.
if (! \MEOW_MUST_USE) {
\add_filter(
'plugin_action_links_' . \plugin_basename(\MEOW_INDEX),
array(static::class, 'plugin_action_links')
);
}
}
/**
* Password Reset Menu
*
* Rather than sending users to the (huge) profile page to reset
* passwords, let's give them something more straight-forward.
*
* @return void Nothing.
*/
public static function retroactive_reset_menu() {
if (login::password_require_reset_needed()) {
\add_submenu_page(
'index.php',
\__('Reset Password', 'apocalypse-meow'),
\__('Reset Password', 'apocalypse-meow'),
'read',
'meow-retroactive-reset',
array(static::class, 'retroactive_reset_page')
);
}
}
/**
* Password Reset Page
*
* @return void Nothing.
*/
public static function retroactive_reset_page() {
require \MEOW_PLUGIN_DIR . 'admin/retroactive-reset.php';
}
/**
* Settings Menu
*
* @return void Nothing.
*/
public static function settings_menu() {
// Send settings.
\add_menu_page(
\__('Settings', 'apocalypse-meow'),
\__('Settings', 'apocalypse-meow'),
'manage_options',
'meow-settings',
array(static::class, 'settings_page'),
'dashicons-meow'
);
}
/**
* Settings Pages
*
* @return void Nothing.
*/
public static function settings_page() {
require \MEOW_PLUGIN_DIR . 'admin/settings.php';
}
/**
* Activity Menu
*
* @return void Nothing.
*/
public static function activity_menu() {
\add_submenu_page(
'meow-settings',
\__('Login Activity', 'apocalypse-meow'),
\__('Login Activity', 'apocalypse-meow'),
'manage_options',
'meow-activity',
array(static::class, 'activity_page')
);
}
/**
* Activity Page
*
* @return void Nothing.
*/
public static function activity_page() {
require \MEOW_PLUGIN_DIR . 'admin/activity.php';
}
/**
* Reference Menu
*
* @return void Nothing.
*/
public static function help_menu() {
// This is only available to Pro users.
if (! options::is_pro()) {
return;
}
\add_submenu_page(
'meow-settings',
\__('Reference', 'apocalypse-meow'),
\__('Reference', 'apocalypse-meow'),
'manage_options',
'meow-help',
array(static::class, 'help_page')
);
}
/**
* Reference Page
*
* @return void Nothing.
*/
public static function help_page() {
require \MEOW_PLUGIN_DIR . 'admin/help.php';
}
/**
* Stats Menu
*
* @return void Nothing.
*/
public static function stats_menu() {
// This is only available to Pro users.
if (! options::is_pro()) {
return;
}
\add_submenu_page(
'meow-settings',
\__('Login Stats', 'apocalypse-meow'),
\__('Login Stats', 'apocalypse-meow'),
'manage_options',
'meow-stats',
array(static::class, 'stats_page')
);
}
/**
* Stats Page
*
* @return void Nothing.
*/
public static function stats_page() {
require \MEOW_PLUGIN_DIR . 'admin/stats.php';
}
/**
* Tools
*
* @return void Nothing.
*/
public static function tools_menu() {
// This is only available to Pro users.
if (! options::is_pro()) {
return;
}
\add_submenu_page(
'meow-settings',
\__('Tools', 'apocalypse-meow'),
\__('Tools', 'apocalypse-meow'),
'manage_options',
'meow-tools',
array(static::class, 'tools_page')
);
}
/**
* Tools Page
*
* @return void Nothing.
*/
public static function tools_page() {
require \MEOW_PLUGIN_DIR . 'admin/tools.php';
}
/**
* Pro License
*
* @return void Nothing.
*/
public static function pro_menu() {
\add_submenu_page(
'meow-settings',
\__('Pro License', 'apocalypse-meow'),
\__('Pro License', 'apocalypse-meow'),
'manage_options',
'meow-pro',
array(static::class, 'pro_page')
);
}
/**
* Pro Page
*
* @return void Nothing.
*/
public static function pro_page() {
require \MEOW_PLUGIN_DIR . 'admin/pro.php';
}
/**
* Rename Menu
*
* We want to change the main menu name but leave the main submenu
* link as is. This requires a bit of a hack after the menu has
* been populated.
*
* @return void Nothing.
*/
public static function rename_menu() {
global $menu;
$tmp = \array_reverse($menu, true);
foreach ($tmp as $k=>$v) {
if (! \is_array($v) || \count($v) < 3) {
continue;
}
if (isset($v[2]) && ('meow-settings' === $v[2])) {
$menu[$k][0] = 'Apocalypse Meow';
break;
}
}
unset($tmp);
}
/**
* Plugin Links
*
* Add some quick links to the entry on the plugins page.
*
* @param array $links Links.
* @return array Links.
*/
public static function plugin_action_links($links) {
// Settings.
$links[] = '' . \__('Settings', 'apocalypse-meow') . '';
// Activity.
$links[] = '' . \__('Activity', 'apocalypse-meow') . '';
// Tools.
if (options::is_pro()) {
$links[] = '' . \__('Tools', 'apocalypse-meow') . '';
}
// Pro.
else {
$links[] = '' . \__('Enter License', 'apocalypse-meow') . '';
}
return $links;
}
// --------------------------------------------------------------------- end menus
// ---------------------------------------------------------------------
// User Columns
// ---------------------------------------------------------------------
/**
* Add User Columns
*
* @param array $columns Columns.
* @return array Columns.
*/
public static function users_columns($columns) {
if (\current_user_can('manage_options')) {
$columns['meow_last_login'] = \__('Last Login', 'apocalypse-meow');
$columns['meow_failed_logins'] = \__('Failed Logins', 'apocalypse-meow');
$columns['meow_registered'] = \__('Registered', 'apocalypse-meow');
}
return $columns;
}
/**
* Add Sortable User Columns
*
* @param array $columns Columns.
* @return array Columns.
*/
public static function users_sortable_columns($columns) {
if (\current_user_can('manage_options')) {
$columns['meow_registered'] = 'user_registered';
}
return $columns;
}
/**
* User Column Values
*
* @param string $value Column value.
* @param string $column Column.
* @param int $user_id User ID.
* @return string Value.
*/
public static function users_custom_column($value, $column, $user_id) {
$user_id = (int) $user_id;
if (\current_user_can('manage_options') && $user_id > 0) {
global $wpdb;
switch ($column) {
case 'meow_last_login':
$dbResult = $wpdb->get_results("
SELECT
l.date_created,
l.ip
FROM
`{$wpdb->users}` AS u,
`{$wpdb->prefix}meow2_log` AS l
WHERE
u.ID=$user_id AND
u.user_login=l.username AND
l.type='success'
ORDER BY l.date_created DESC
LIMIT 1
", \ARRAY_A);
if (\is_array($dbResult) && \count($dbResult)) {
$Row = common\data::array_pop_top($dbResult);
common\ref\sanitize::ip($Row['ip']);
$value = static::get_column_date($Row['date_created']);
if ($Row['ip']) {
$value .= "
{$Row['ip']}";
}
}
break;
case 'meow_failed_logins':
$dbResult = $wpdb->get_results("
SELECT
MIN(l.date_created) AS `date_min`,
MAX(l.date_created) AS `date_max`,
COUNT(*) AS `fails`
FROM
`{$wpdb->users}` AS u,
`{$wpdb->prefix}meow2_log` AS l
WHERE
u.ID=$user_id AND
u.user_login=l.username AND
l.type='fail'
GROUP BY u.ID
", \ARRAY_A);
if (\is_array($dbResult) && \count($dbResult)) {
$Row = common\data::array_pop_top($dbResult);
$value = (int) $Row['fails'];
$first = static::get_column_date($Row['date_min']);
$last = static::get_column_date($Row['date_max']);
// Multiple times.
if (\strip_tags($first) !== \strip_tags($last)) {
$value .= "
$first - $last";
}
// One time.
else {
$value .= "
$first";
}
}
else {
$value = 0;
}
break;
case 'meow_registered':
if (false !== ($user = \get_userdata($user_id))) {
$value = static::get_column_date($user->user_registered);
}
break;
}
}
return $value;
}
/**
* Column Time Formatter
*
* This formats dates the same way other admin columns do, using an
* tag to cut down on the display real estate.
*
* @param string $date Datetime.
* @param bool $utc UTC.
* @return string HTML.
*/
protected static function get_column_date($date, $utc=false) {
$time = \strtotime($date);
$t_time = \date(\__('Y/m/d g:i:s a'), $time);
$now = $utc ? \time() : \current_time('timestamp');
$time_diff = $now - $time;
// Relative time.
if ($time_diff > 0 && $time_diff < \DAY_IN_SECONDS) {
$h_time = \sprintf(
\__('%s ago'),
\human_time_diff($now, $time)
);
}
// A date.
else {
$h_time = \mysql2date(\__('Y/m/d'), $date);
}
return '' . $h_time . '';
}
// --------------------------------------------------------------------- end user cols
}