Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/machete/inc/about/admin-content.php
+++ b/machete/inc/about/admin-content.php
@@ -48,31 +48,6 @@
if ( 'about' === $machete_slug ) {
continue;
}
- if (
- in_array( $machete_slug, array( 'about', 'powertools' ), true ) ||
- $machete_args['is_external']
- ) {
- continue;
- }
- include plugin_dir_path( __FILE__ ) . 'templates/machete-module.php';
- }
- ?>
- </div>
-
- <h2><?php esc_html_e( 'External Modules', 'machete' ); ?></h2>
-
- <div class="machete-module-list">
- <?php
- foreach ( $machete->modules as $machete_module ) {
- $machete_args = $machete_module->params;
- $machete_slug = $machete_args['slug'];
-
- if ( 'about' === $machete_slug ) {
- continue;
- }
- if ( ( 'powertools' !== $machete_slug ) && ( ! $machete_args['is_external'] ) ) {
- continue;
- }
include plugin_dir_path( __FILE__ ) . 'templates/machete-module.php';
}
?>
--- a/machete/inc/about/templates/machete-module.php
+++ b/machete/inc/about/templates/machete-module.php
@@ -15,7 +15,7 @@
$machete_args['banner'] = MACHETE_BASE_URL . 'inc/' . $machete_slug . '/banner.svg';
}
?>
-<div class="machete-module-wrap"><div class="machete-module <?php echo esc_attr( $machete_slug . '-module' ); ?> module-is-<?php echo $machete_args['is_active'] ? 'active' : 'inactive'; ?>">
+<div class="machete-module-wrap"><div class="machete-module <?php echo esc_attr( $machete_slug . '-module' ); ?><?php echo ! empty( $machete_args['has_warning'] ) ? ' has-warning-module' : ''; ?> module-is-<?php echo $machete_args['is_active'] ? 'active' : 'inactive'; ?>">
<?php if ( $machete_args['is_active'] && $machete_args['has_config'] ) { ?>
<a class="machete-module-image"
@@ -59,54 +59,8 @@
'machete_action_' . $machete_slug
);
- if ( 'powertools' === $machete_slug ) {
-
- $machete_powertools_path = 'machete-powertools/machete-powertools.php';
-
- if ( $machete_args['is_active'] ) {
-
- $machete_powertools_deactivation_url = wp_nonce_url(
- add_query_arg(
- array(
- 'action' => 'deactivate',
- 'plugin' => $machete_powertools_path,
- ),
- admin_url( 'plugins.php' )
- ),
- 'deactivate-plugin_' . $machete_powertools_path
- );
- ?>
-
- <a href="<?php echo esc_url( $machete_powertools_deactivation_url ); ?>" class="button-secondary" data-status="1"><?php esc_html_e( 'Deactivate Plugin', 'machete' ); ?></a>
-
- <a href="<?php echo esc_url( add_query_arg( 'page', 'machete-' . $machete_slug, admin_url( 'admin.php' ) ) ); ?>"
- title="<?php echo esc_attr( __( 'Configure', 'machete' ) . ' ' . $machete_args['full_title'] ); ?>" class="button-secondary" data-status="0"><?php esc_html_e( 'Settings', 'machete' ); ?></a>
-
- <?php
- } elseif ( file_exists( WP_PLUGIN_DIR . '/' . $machete_powertools_path ) ) {
-
- $machete_powertools_activation_url = wp_nonce_url(
- add_query_arg(
- array(
- 'action' => 'activate',
- 'plugin' => $machete_powertools_path,
- ),
- admin_url( 'plugins.php' )
- ),
- 'activate-plugin_' . $machete_powertools_path
- );
- ?>
-
- <a href="<?php echo esc_url( $machete_powertools_activation_url ); ?>" class="button-primary" data-status="1"><?php esc_html_e( 'Activate Plugin', 'machete' ); ?></a>
-
- <?php } else { ?>
-
- <a href="https://machetewp.com/powertools/" class="button-primary" data-status="1"><?php esc_html_e( 'Download Machete PowerTools', 'machete' ); ?></a>
-
- <?php } ?>
-
-
- <?php } elseif ( $machete_args['is_active'] ) { ?>
+ if ( $machete_args['is_active'] ) {
+ ?>
<?php if ( $machete_args['can_be_disabled'] ) { ?>
<a href="<?php echo esc_url( add_query_arg( 'machete-action', 'deactivate', $machete_action_url ) ); ?>" class="button-secondary" data-status="0"><?php esc_html_e( 'Deactivate', 'machete' ); ?></a>
@@ -119,7 +73,21 @@
<?php } else { ?>
<?php if ( $machete_args['can_be_enabled'] ) { ?>
- <a href="<?php echo esc_url( add_query_arg( 'machete-action', 'activate', $machete_action_url ) ); ?>" class="button-secondary" data-status="1"><?php esc_html_e( 'Activate', 'machete' ); ?></a>
+ <?php
+ $machete_activate_url = add_query_arg( 'machete-action', 'activate', $machete_action_url );
+ $machete_activate_class = 'button-secondary';
+ if ( ! empty( $machete_args['has_warning'] ) ) {
+ $machete_activate_class .= ' machete-module-activate-warning';
+ }
+ ?>
+ <a href="<?php echo esc_url( $machete_activate_url ); ?>"
+ class="<?php echo esc_attr( $machete_activate_class ); ?>"
+ data-status="1"
+ <?php if ( ! empty( $machete_args['has_warning'] ) ) { ?>
+ data-warning-title="<?php echo esc_attr( wp_strip_all_tags( $machete_args['full_title'] ) ); ?>"
+ data-warning-message="<?php echo esc_attr( $machete_args['activation_warning'] ); ?>"
+ data-warning-icon-url="<?php echo esc_url( MACHETE_BASE_URL . 'inc/' . $machete_slug . '/icon.svg' ); ?>"
+ <?php } ?>><?php esc_html_e( 'Activate', 'machete' ); ?></a>
<?php } else { ?>
<span class="button-secondary button-disabled"><?php esc_html_e( 'Activate', 'machete' ); ?></span>
<?php } ?>
--- a/machete/inc/class-machete-module.php
+++ b/machete/inc/class-machete-module.php
@@ -24,16 +24,17 @@
* @var array
*/
public $params = array(
- 'slug' => '',
- 'title' => '',
- 'full_title' => '',
- 'description' => '',
- 'is_external' => false,
- 'is_active' => true,
- 'has_config' => true,
- 'can_be_disabled' => true,
- 'can_be_enabled' => true,
- 'role' => 'manage_options',
+ 'slug' => '',
+ 'title' => '',
+ 'full_title' => '',
+ 'description' => '',
+ 'is_active' => true,
+ 'has_warning' => false,
+ 'activation_warning' => '',
+ 'has_config' => true,
+ 'can_be_disabled' => true,
+ 'can_be_enabled' => true,
+ 'role' => 'manage_options',
);
/**
* Temporal container for the module's database-stored settings
@@ -70,7 +71,8 @@
* @param array $params params array with the immutable module properties.
*/
protected function init( $params = array() ) {
- $this->params = array_merge( $this->params, $params );
+ $this->params = array_merge( $this->params, $params );
+ $this->params['is_active_default'] = $this->params['is_active'];
if ( array_key_exists( 'path', $this->params ) ) {
$this->path = $this->params['path'];
} else {
--- a/machete/inc/class-machete.php
+++ b/machete/inc/class-machete.php
@@ -145,10 +145,10 @@
return false;
}
- $disabled_modules = get_option( 'machete_disabled_modules', array() );
+ $enabled_modules = get_option( 'machete_enabled_modules', array() );
if ( 'deactivate' === $action ) {
- if ( in_array( $module, $disabled_modules, true ) ) {
+ if ( ! $this->modules[ $module ]->params['is_active'] ) {
if ( ! $silent ) {
$this->notice( __( 'Nothing to do. The module was already disabled.', 'machete' ), 'notice' );
}
@@ -161,9 +161,10 @@
return false;
}
- $disabled_modules[] = $module;
+ $enabled_modules = array_values( array_diff( $enabled_modules, array( $module ) ) );
+ update_option( 'machete_enabled_modules', $enabled_modules );
- if ( update_option( 'machete_disabled_modules', $disabled_modules ) ) {
+ if ( ! in_array( $module, get_option( 'machete_enabled_modules', array() ), true ) ) {
$this->modules[ $module ]->params['is_active'] = false;
if ( ! $silent ) {
$this->notice(
@@ -191,16 +192,13 @@
}
return false;
}
- if ( 'powertools' === $module ) {
- if ( ! $silent ) {
- $this->notice( __( 'Sorry, you can't enable that module', 'machete' ), 'warning' );
- }
- return false;
- }
- $disabled_modules = array_diff( $disabled_modules, array( $module ) );
+ if ( ! in_array( $module, $enabled_modules, true ) ) {
+ $enabled_modules[] = $module;
+ }
+ update_option( 'machete_enabled_modules', $enabled_modules );
- if ( update_option( 'machete_disabled_modules', $disabled_modules ) ) {
+ if ( in_array( $module, get_option( 'machete_enabled_modules', array() ), true ) ) {
$this->modules[ $module ]->params['is_active'] = true;
if ( ! $silent ) {
$this->notice(
--- a/machete/inc/cookies/class-machete-cookies-module.php
+++ b/machete/inc/cookies/class-machete-cookies-module.php
@@ -114,12 +114,13 @@
$html_replaces['{{theme_stylesheet}}'] = $this->themes[ $options['bar_theme'] ]['stylesheet'];
// Cookie bar accent color.
- if ( ! empty( $options['accent_color'] ) ) {
-
- $settings['accent_color'] = $options['accent_color'];
- if ( $options['accent_color'] !== $this->default_settings['accent_color'] ) {
- $this->cookies_bar_innerhtml .= 'var machete_cookies_accent_color = '' . $options['accent_color'] . '';' . "n";
- }
+ $accent_color = sanitize_hex_color( $options['accent_color'] ?? '' );
+ if ( empty( $accent_color ) ) {
+ $accent_color = $this->default_settings['accent_color'];
+ }
+ $settings['accent_color'] = $accent_color;
+ if ( $accent_color !== $this->default_settings['accent_color'] ) {
+ $this->cookies_bar_innerhtml .= 'var machete_cookies_accent_color = '' . esc_js( $accent_color ) . '';' . "n";
}
// Cookie bar status.
--- a/machete/inc/powertools/admin-content.php
+++ b/machete/inc/powertools/admin-content.php
@@ -11,51 +11,141 @@
}
$machete_allowed_description_tags = array(
- 'br' => array(),
- 'span' => array(
+ 'a' => array(
+ 'href' => array(),
+ 'target' => array(),
+ 'rel' => array(),
+ ),
+ 'br' => array(),
+ 'strong' => array(),
+ 'span' => array(
'style' => array(),
),
);
+$machete_expired_transients_count = $this->count_expired_transients();
+$machete_post_revisions_count = $this->count_post_revisions();
+$machete_orphaned_postmeta_count = $this->count_orphaned_postmeta();
+$machete_expired_cron_count = $this->count_expired_cron_events();
+
?>
<div class="wrap machete-wrap machete-section-wrap">
<div class="wp-header-end"></div><!-- admin notices go after .wp-header-end or .wrap>h2:first-child -->
<h1><?php $this->icon(); ?> <?php esc_html_e( 'Machete PowerTools', 'machete' ); ?></h1>
- <p class="tab-description"><?php esc_html_e( 'Machete PowerTools is an upgrade module targeted at WordPress developers and power users. PowerTools adds new features and improves some of the Machete modules you already know and love.', 'machete' ); ?></p>
+ <p class="tab-description"><?php esc_html_e( 'PowerTools bundles advanced utilities and maintenance actions for WordPress developers and power users. As with every Machete tool, do not enable any option you do not understand.', 'machete' ); ?></p>
<?php $machete->admin_tabs( 'machete-powertools' ); ?>
<p class="tab-performance"><span><strong><i class="dashicons dashicons-clock"></i> <?php esc_html_e( 'Performance impact:', 'machete' ); ?></strong> <?php esc_html_e( 'This section stores all its settings in a single autoloaded configuration variable.', 'machete' ); ?></span></p>
-<form id="mache-powertools-actions" action="" method="POST">
+<form id="machete-powertools-actions" action="" method="POST">
<?php wp_nonce_field( 'machete_powertools_action' ); ?>
- <table class="form-table">
+ <table class="form-table machete-powertools-actions-table">
<tbody><tr>
- <th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete Expired Transients', 'machete' ); ?></label></th>
- <td><input type="submit" name="machete-powertools-action" value="purge_transients" class="button button-primary">
- <p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="purge_transients" value="purge_transients" class="button button-primary"><?php esc_html_e( 'Purge transients', 'machete' ); ?></button></th>
+ <td><label for="purge_transients"><?php
+ echo wp_kses(
+ '<strong>' . sprintf(
+ /* translators: %1$s is the number of expired transients */
+ _n(
+ 'Remove %1$s expired transient from wp_options.',
+ 'Remove %1$s expired transients from wp_options.',
+ $machete_expired_transients_count,
+ 'machete'
+ ),
+ number_format_i18n( $machete_expired_transients_count )
+ ) . '</strong><br>' . __( 'These are temporary cache entries that WordPress and plugins should have deleted automatically.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
+ </tr>
+ <tr>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="purge_post_revisions" value="purge_post_revisions" class="button button-primary"><?php esc_html_e( 'Purge post revisions', 'machete' ); ?></button></th>
+ <td><label for="purge_post_revisions"><?php
+ echo wp_kses(
+ '<strong>' . sprintf(
+ /* translators: %1$s is the number of post revisions */
+ _n(
+ 'Remove %1$s post revision from the database.',
+ 'Remove %1$s post revisions from the database.',
+ $machete_post_revisions_count,
+ 'machete'
+ ),
+ number_format_i18n( $machete_post_revisions_count )
+ ) . '</strong><br>' . __( 'Revisions are automatic snapshots saved each time a post or page is edited.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
+ </tr>
+ <tr>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="purge_orphaned_meta" value="purge_orphaned_meta" class="button button-primary"><?php esc_html_e( 'Purge orphaned meta', 'machete' ); ?></button></th>
+ <td><label for="purge_orphaned_meta"><?php
+ echo wp_kses(
+ '<strong>' . sprintf(
+ /* translators: %1$s is the number of orphaned postmeta rows */
+ _n(
+ 'Remove %1$s orphaned postmeta row.',
+ 'Remove %1$s orphaned postmeta rows.',
+ $machete_orphaned_postmeta_count,
+ 'machete'
+ ),
+ number_format_i18n( $machete_orphaned_postmeta_count )
+ ) . '</strong><br>' . __( 'These custom field records reference a post ID that no longer exists.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
+ </tr>
+ <tr>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="purge_expired_cron" value="purge_expired_cron" class="button button-primary"><?php esc_html_e( 'Purge expired Cron', 'machete' ); ?></button></th>
+ <td><label for="purge_expired_cron"><?php
+ echo wp_kses(
+ '<strong>' . sprintf(
+ /* translators: %1$s is the number of expired cron events */
+ _n(
+ 'Remove %1$s expired cron event from wp_options.',
+ 'Remove %1$s expired cron events from wp_options.',
+ $machete_expired_cron_count,
+ 'machete'
+ ),
+ number_format_i18n( $machete_expired_cron_count )
+ ) . '</strong><br>' . __( 'These scheduled tasks were missed, usually because WP-Cron did not run on time.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
</tr>
<tr>
- <th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete Permalink Cache', 'machete' ); ?></label></th>
- <td><input type="submit" name="machete-powertools-action" value="flush_rewrites" class="button button-primary">
- <p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="flush_rewrites" value="flush_rewrites" class="button button-primary">flush_rewrite_rules()</button></th>
+ <td><label for="flush_rewrites"><?php
+ echo wp_kses(
+ '<strong>' . __( 'Regenerate WordPress permalink rewrite rules.', 'machete' ) . '</strong><br>' . __( 'Use this after changing permalink settings, registering custom post types, or when valid URLs return 404 errors.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
</tr>
<?php if ( function_exists( 'opcache_reset' ) ) { ?>
<tr>
- <th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete Opcache contents', 'machete' ); ?></label></th>
- <td><input type="submit" name="machete-powertools-action" value="flush_opcache" class="button button-primary">
- <p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="flush_opcache" value="flush_opcache" class="button button-primary">opcache_reset()</button></th>
+ <td><label for="flush_opcache"><?php
+ echo wp_kses(
+ '<strong>' . __( 'Clear the PHP OPcache bytecode cache.', 'machete' ) . '</strong><br>' . __( 'Use this after deploying PHP changes when the server keeps serving an older version of your files.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
</tr>
<?php } ?>
<tr>
- <th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete WordPress object cache contents', 'machete' ); ?></label></th>
- <td><input type="submit" name="machete-powertools-action" value="flush_wpcache" class="button button-primary">
- <p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+ <th scope="row"><button type="submit" name="machete-powertools-action" id="flush_wpcache" value="flush_wpcache" class="button button-primary">wp_cache_flush()</button></th>
+ <td><label for="flush_wpcache"><?php
+ echo wp_kses(
+ '<strong>' . __( 'Flush the WordPress object cache.', 'machete' ) . '</strong><br>' . __( 'Use this when a persistent cache (Redis, Memcached, etc.) keeps serving stale data after updates.', 'machete' ),
+ $machete_allowed_description_tags
+ );
+ ?></label></td>
</tr>
</tbody></table>
@@ -74,8 +164,8 @@
<table class="wp-list-table widefat fixed striped posts machete-options-table machete-powertools-table">
<thead>
<tr>
- <td class="manage-column column-cb check-column " ><input type="checkbox" name="check_all" id="machete_cleanup_checkall_fld" <?php checked( true, $this->all_powertools_checked, true ); ?>></td>
- <th class="column-title manage-column column-primary"><?php esc_html_e( 'Remove', 'machete' ); ?></th>
+ <td class="manage-column column-cb check-column " ><input type="checkbox" name="check_all" id="machete_powertools_checkall_fld" <?php checked( true, $this->all_powertools_checked, true ); ?>></td>
+ <th class="column-title manage-column column-primary"><?php esc_html_e( 'Enable', 'machete' ); ?></th>
<th><?php esc_html_e( 'Explanation', 'machete' ); ?></th>
</tr>
</thead>
@@ -104,24 +194,13 @@
( function( $ ) {
$('#machete-powertools-options .machete-powertools-table :checkbox').change(function() {
- // this will contain a reference to the checkbox
- console.log(this.id);
var checkBoxes = $("#machete-powertools-options .machete-powertools-table input[name=optionEnabled\[\]]");
- if ( this.id == 'machete_powertools_checkall_fld' ){
- if (this.checked) {
- checkBoxes.prop( "checked" , true );
- } else {
- checkBoxes.prop( "checked", false );
- // the checkbox is now no longer checked
- }
+ if ( this.id === 'machete_powertools_checkall_fld' ) {
+ checkBoxes.prop( 'checked', this.checked );
} else {
var checkBoxes_checked = $("#machete-powertools-options .machete-powertools-table input[name=optionEnabled\[\]]:checked");
- if(checkBoxes_checked.length == checkBoxes.length){
- $('#machete_powertools_checkall_fld').prop("checked", true);
- } else {
- $('#machete_powertools_checkall_fld').prop("checked", false);
- }
+ $('#machete_powertools_checkall_fld').prop( 'checked', checkBoxes_checked.length === checkBoxes.length );
}
});
})( jQuery );
--- a/machete/inc/powertools/class-machete-powertools-module.php
+++ b/machete/inc/powertools/class-machete-powertools-module.php
@@ -25,7 +25,8 @@
array(
'slug' => 'powertools',
'is_active' => false,
- 'can_be_disabled' => false,
+ 'has_warning' => true,
+ 'can_be_disabled' => true,
)
);
@@ -37,7 +38,9 @@
'defer_all_scripts' => array(),
'disable_feeds' => array(),
'enable_svg' => array(),
- 'disable_search' => array(),
+ 'disable_search' => array(),
+ 'show_admin_ids' => array(),
+ 'disable_admin_bar_frontend' => array(),
);
}
/**
@@ -73,6 +76,15 @@
case 'purge_transients':
$this->purge_transients();
break;
+ case 'purge_post_revisions':
+ $this->purge_post_revisions();
+ break;
+ case 'purge_orphaned_meta':
+ $this->purge_orphaned_meta();
+ break;
+ case 'purge_expired_cron':
+ $this->purge_expired_cron();
+ break;
case 'flush_rewrites':
$this->flush_rewrite_rules();
break;
@@ -93,8 +105,39 @@
require $this->path . 'powertools.php';
}
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
+
add_action( 'admin_menu', array( $this, 'register_sub_menu' ) );
}
+
+ /**
+ * Enqueues PowerTools admin styles on the module page and when list-table options are active.
+ *
+ * @param string $hook_suffix Current admin page hook suffix.
+ */
+ public function enqueue_admin_styles( $hook_suffix ) {
+ $is_powertools_page = ( 'machete_page_machete-powertools' === $hook_suffix );
+
+ if ( ! $is_powertools_page && ! $this->needs_admin_styles() ) {
+ return;
+ }
+
+ wp_enqueue_style(
+ 'machete-powertools-admin',
+ MACHETE_BASE_URL . 'inc/powertools/css/admin.css',
+ array(),
+ MACHETE_VERSION
+ );
+ }
+
+ /**
+ * Whether any active option needs the PowerTools admin stylesheet.
+ *
+ * @return bool
+ */
+ private function needs_admin_styles() {
+ return in_array( 'show_admin_ids', $this->settings, true );
+ }
/**
* Saves options to database
*
@@ -164,7 +207,9 @@
private function purge_transients() {
global $wpdb;
- $rows = $wpdb->query(
+ $count = $this->count_expired_transients();
+
+ $wpdb->query(
$wpdb->prepare(
"DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
@@ -175,9 +220,9 @@
$wpdb->esc_like( '_transient_timeout_' ) . '%',
time()
)
- ); // phpcs: cache ok, db call ok.
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
- $rows2 = $wpdb->query(
+ $wpdb->query(
$wpdb->prepare(
"DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
@@ -188,28 +233,181 @@
$wpdb->esc_like( '_site_transient_timeout_' ) . '%',
time()
)
- ); // phpcs: cache ok, db call ok.
- // translators: $d number of deleted transsients.
- $this->notice( sprintf( __( '%d Transients Rows Cleared', 'machete' ), $rows + $rows2 ), 'success' );
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ // translators: %s: number of deleted expired transients.
+ $this->notice( sprintf( _n( '%s expired transient cleared.', '%s expired transients cleared.', $count, 'machete' ), number_format_i18n( $count ) ), 'success' );
return true;
}
/**
- * Deletes all unused post revisions.
+ * Returns the number of expired transients.
+ *
+ * @return int
+ */
+ public function count_expired_transients() {
+ global $wpdb;
+
+ $time = time();
+
+ $count = (int) $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT(*) FROM $wpdb->options a, $wpdb->options b
+ WHERE a.option_name LIKE %s
+ AND a.option_name NOT LIKE %s
+ AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
+ AND b.option_value < %d",
+ $wpdb->esc_like( '_transient_' ) . '%',
+ $wpdb->esc_like( '_transient_timeout_' ) . '%',
+ $time
+ )
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ $count += (int) $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT(*) FROM $wpdb->options a, $wpdb->options b
+ WHERE a.option_name LIKE %s
+ AND a.option_name NOT LIKE %s
+ AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
+ AND b.option_value < %d",
+ $wpdb->esc_like( '_site_transient_' ) . '%',
+ $wpdb->esc_like( '_site_transient_timeout_' ) . '%',
+ $time
+ )
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ return $count;
+ }
+
+ /**
+ * Returns the number of post revisions.
+ *
+ * @return int
+ */
+ public function count_post_revisions() {
+ global $wpdb;
+
+ return (int) $wpdb->get_var(
+ "SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = 'revision'"
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ }
+
+ /**
+ * Returns the number of orphaned postmeta rows.
+ *
+ * @return int
+ */
+ public function count_orphaned_postmeta() {
+ global $wpdb;
+
+ return (int) $wpdb->get_var(
+ "SELECT COUNT(*) FROM $wpdb->postmeta pm
+ LEFT JOIN $wpdb->posts p ON p.ID = pm.post_id
+ WHERE p.ID IS NULL"
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ }
+
+ /**
+ * Returns the number of expired cron events.
+ *
+ * @return int
+ */
+ public function count_expired_cron_events() {
+ if ( ! function_exists( '_get_cron_array' ) ) {
+ require_once ABSPATH . 'wp-includes/cron.php';
+ }
+
+ $cron = _get_cron_array();
+ $count = 0;
+ $time = time();
+
+ if ( empty( $cron ) ) {
+ return 0;
+ }
+
+ foreach ( $cron as $timestamp => $hooks ) {
+ if ( (int) $timestamp >= $time ) {
+ continue;
+ }
+ foreach ( $hooks as $events ) {
+ $count += count( $events );
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Deletes all post revisions and their related meta/term rows.
*/
private function purge_post_revisions() {
global $wpdb;
+ $wpdb->query(
+ "DELETE pm FROM $wpdb->postmeta pm
+ INNER JOIN $wpdb->posts p ON pm.post_id = p.ID
+ WHERE p.post_type = 'revision'"
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ $wpdb->query(
+ "DELETE tr FROM $wpdb->term_relationships tr
+ INNER JOIN $wpdb->posts p ON tr.object_id = p.ID
+ WHERE p.post_type = 'revision'"
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ $rows = $wpdb->query(
+ "DELETE FROM $wpdb->posts WHERE post_type = 'revision'"
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ // translators: %s: number of deleted post revisions.
+ $this->notice( sprintf( _n( 'Success! %s post revision deleted.', 'Success! %s post revisions deleted.', $rows, 'machete' ), number_format_i18n( $rows ) ), 'success' );
+ return true;
+ }
+
+ /**
+ * Deletes postmeta rows whose post no longer exists.
+ */
+ private function purge_orphaned_meta() {
+ global $wpdb;
+
$rows = $wpdb->query(
- "DELETE a,b,c
- FROM wp_posts a
- WHERE a.post_type = 'revision'
- LEFT JOIN wp_term_relationships b
- ON (a.ID = b.object_id)
- LEFT JOIN wp_postmeta c ON (a.ID = c.post_id);"
- ); // phpcs: cache ok, db call ok.
- // translators: $d number of deleted post revisions.
- $this->notice( sprintf( _n( 'Success! %s Post revision deleted.', 'Success! %s Post revisions deleted.', $rows, 'machete' ), $rows ), 'success' );
+ "DELETE pm FROM $wpdb->postmeta pm
+ LEFT JOIN $wpdb->posts p ON p.ID = pm.post_id
+ WHERE p.ID IS NULL"
+ ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ // translators: %s: number of deleted postmeta rows.
+ $this->notice( sprintf( _n( '%s orphaned postmeta row deleted.', '%s orphaned postmeta rows deleted.', $rows, 'machete' ), number_format_i18n( $rows ) ), 'success' );
+ return true;
+ }
+
+ /**
+ * Removes cron events scheduled in the past.
+ */
+ private function purge_expired_cron() {
+ if ( ! function_exists( '_get_cron_array' ) ) {
+ require_once ABSPATH . 'wp-includes/cron.php';
+ }
+
+ $cron = _get_cron_array();
+ $removed = 0;
+ $time = time();
+
+ if ( ! empty( $cron ) ) {
+ foreach ( $cron as $timestamp => $hooks ) {
+ if ( (int) $timestamp >= $time ) {
+ continue;
+ }
+ foreach ( $hooks as $events ) {
+ $removed += count( $events );
+ }
+ unset( $cron[ $timestamp ] );
+ }
+ _set_cron_array( $cron );
+ }
+
+ // translators: %s: number of removed cron events.
+ $this->notice( sprintf( _n( '%s expired cron event removed.', '%s expired cron events removed.', $removed, 'machete' ), number_format_i18n( $removed ) ), 'success' );
return true;
}
/**
--- a/machete/inc/powertools/i18n.php
+++ b/machete/inc/powertools/i18n.php
@@ -10,14 +10,15 @@
exit;
}
-$this->params['title'] = '<span style="color: #ff9900">' . __( 'PowerTools', 'machete' ) . '</span>';
-$this->params['full_title'] = __( 'Machete PowerTools', 'machete' );
-$this->params['description'] = __( 'Machete PowerTools is a free upgrade module targeted at WordPress developers and power users.', 'machete' );
+$this->params['title'] = '<span style="color: #ff9900">' . __( 'PowerTools', 'machete' ) . '</span>';
+$this->params['full_title'] = __( 'Machete PowerTools', 'machete' );
+$this->params['description'] = __( 'Advanced tools for WordPress developers and power users. Disabled by default.', 'machete' );
+$this->params['activation_warning'] = __( 'This module contains options that can break your site. Only use them if you know what you are doing.', 'machete' );
/* Powertools options array */
$this->powertools_array['widget_shortcodes'] = array(
'title' => __( 'Shortcodes in Widgets', 'machete' ),
- 'description' => __( 'Enables the use of shortcodes in text/html widgets. It may slightly impact performance', 'machete' ),
+ 'description' => __( 'Enables shortcodes in classic Text and Custom HTML widgets.', 'machete' ) . '<br>' . __( 'For block-based widget areas (WordPress 5.8+), use the Shortcode block instead. Only needed on legacy themes and classic widget setups.', 'machete' ),
);
$this->powertools_array['rss_thumbnails'] = array(
@@ -53,5 +54,15 @@
$this->powertools_array['disable_search'] = array(
'title' => __( 'Disable search', 'machete' ),
- 'description' => __( 'Disables the public search from WordPress', 'machete' ),
+ 'description' => __( 'Disables public search on the frontend. Search URLs return a 404, search forms are hidden, and the Search widget is removed from widget areas.', 'machete' ) . '<br>' . __( 'Does not affect admin search. Useful for brochure sites and layouts that do not need public search.', 'machete' ),
+);
+
+$this->powertools_array['show_admin_ids'] = array(
+ 'title' => __( 'Show IDs in admin lists', 'machete' ),
+ 'description' => __( 'Adds an ID column to post, page, taxonomy and user list screens in the WordPress admin.', 'machete' ),
+);
+
+$this->powertools_array['disable_admin_bar_frontend'] = array(
+ 'title' => __( 'Disable admin bar on frontend', 'machete' ),
+ 'description' => __( 'Hides the WordPress admin bar on the frontend for all users except administrators.', 'machete' ),
);
--- a/machete/inc/powertools/powertools.php
+++ b/machete/inc/powertools/powertools.php
@@ -20,6 +20,8 @@
disable_feeds
enable_svg
disable_search
+show_admin_ids
+disable_admin_bar_frontend
*/
// enable shortcodes in widgets.
@@ -30,15 +32,50 @@
// enable rss thumbnails.
if ( in_array( 'rss_thumbnails', $this->settings, true ) && ! is_admin() ) {
/**
- * Adds the featured image before the content.
+ * Returns the featured image or the first attached image HTML for RSS feeds.
+ *
+ * @param int $post_id Post ID.
+ * @return string
+ */
+ function machete_get_rss_thumbnail_html( $post_id ) {
+ if ( has_post_thumbnail( $post_id ) ) {
+ return get_the_post_thumbnail( $post_id, 'full' );
+ }
+
+ $attachments = get_children(
+ array(
+ 'post_parent' => $post_id,
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ 'post_mime_type' => 'image',
+ 'orderby' => 'menu_order',
+ 'order' => 'ASC',
+ 'numberposts' => 1,
+ )
+ );
+
+ if ( empty( $attachments ) ) {
+ return '';
+ }
+
+ $attachment = array_shift( $attachments );
+
+ return wp_get_attachment_image( $attachment->ID, 'full' );
+ }
+
+ /**
+ * Adds the featured or first attached image before the content.
*
* @param string $content post content.
*/
function machete_add_rss_thumbnail( $content ) {
global $post;
- if ( has_post_thumbnail( $post->ID ) ) {
- $content = '<div class="post-thumbnail-feed">' . get_the_post_thumbnail( $post->ID, 'full' ) . '</div>' . $content;
+
+ $thumbnail_html = machete_get_rss_thumbnail_html( $post->ID );
+ if ( '' !== $thumbnail_html ) {
+ $content = '<div class="post-thumbnail-feed">' . $thumbnail_html . '</div>' . $content;
}
+
return $content;
}
add_filter( 'the_excerpt_rss', 'machete_add_rss_thumbnail' );
@@ -115,35 +152,152 @@
// disable Search.
if ( in_array( 'disable_search', $this->settings, true ) ) {
- /**
- * Removes search.
- *
- * @param WP_Query $query The query object that parsed the query.
- */
add_action(
'parse_query',
function ( $query, $error = true ) {
- if ( is_search() ) {
- $query->is_search = false;
- $query->query_vars['s'] = false;
- $query->query['s'] = false;
- if ( true === $error ) {
- $query->is_404 = true;
- }
+ if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
+ return;
+ }
+
+ $query->is_search = false;
+ $query->query_vars['s'] = false;
+ $query->query['s'] = false;
+ if ( true === $error ) {
+ $query->is_404 = true;
}
}
);
add_filter(
'get_search_form',
- // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
- function ( $a ) {
+ function ( $form ) {
+ if ( is_admin() ) {
+ return $form;
+ }
return null;
}
);
add_action(
'widgets_init',
function () {
+ if ( is_admin() ) {
+ return;
+ }
unregister_widget( 'WP_Widget_Search' );
}
);
}
+
+// Show post, term and user IDs in admin list tables.
+if ( in_array( 'show_admin_ids', $this->settings, true ) && is_admin() ) {
+ /**
+ * Inserts an ID column after the primary list column.
+ *
+ * @param array $columns List table columns.
+ * @return array
+ */
+ function machete_add_admin_id_column( $columns ) {
+ $new_columns = array();
+ $inserted = false;
+
+ foreach ( $columns as $key => $label ) {
+ $new_columns[ $key ] = $label;
+ if ( ! $inserted && in_array( $key, array( 'title', 'name', 'username', 'cb' ), true ) ) {
+ $new_columns['machete_id'] = __( 'ID', 'machete' );
+ $inserted = true;
+ }
+ }
+
+ if ( ! $inserted ) {
+ $new_columns['machete_id'] = __( 'ID', 'machete' );
+ }
+
+ return $new_columns;
+ }
+
+ /**
+ * Renders the post ID column value.
+ *
+ * @param string $column_name Column slug.
+ * @param int $post_id Post ID.
+ */
+ function machete_render_admin_post_id_column( $column_name, $post_id ) {
+ if ( 'machete_id' === $column_name ) {
+ echo esc_html( (string) $post_id );
+ }
+ }
+
+ /**
+ * Renders the term ID column value.
+ *
+ * @param string $string Column output.
+ * @param string $column_name Column slug.
+ * @param int $term_id Term ID.
+ * @return string
+ */
+ function machete_render_admin_term_id_column( $string, $column_name, $term_id ) {
+ if ( 'machete_id' === $column_name ) {
+ return esc_html( (string) $term_id );
+ }
+ return $string;
+ }
+
+ /**
+ * Renders the user ID column value.
+ *
+ * @param string $value Column output.
+ * @param string $column_name Column slug.
+ * @param int $user_id User ID.
+ * @return string
+ */
+ function machete_render_admin_user_id_column( $value, $column_name, $user_id ) {
+ if ( 'machete_id' === $column_name ) {
+ return esc_html( (string) $user_id );
+ }
+ return $value;
+ }
+
+ add_action(
+ 'admin_init',
+ function () {
+ $post_types = get_post_types(
+ array(
+ 'show_ui' => true,
+ ),
+ 'names'
+ );
+
+ foreach ( $post_types as $post_type ) {
+ add_filter( "manage_{$post_type}_posts_columns", 'machete_add_admin_id_column' );
+ add_action( "manage_{$post_type}_posts_custom_column", 'machete_render_admin_post_id_column', 10, 2 );
+ }
+
+ $taxonomies = get_taxonomies(
+ array(
+ 'show_ui' => true,
+ ),
+ 'names'
+ );
+
+ foreach ( $taxonomies as $taxonomy ) {
+ add_filter( "manage_edit-{$taxonomy}_columns", 'machete_add_admin_id_column' );
+ add_filter( "manage_{$taxonomy}_custom_column", 'machete_render_admin_term_id_column', 10, 3 );
+ }
+ }
+ );
+
+ add_filter( 'manage_users_columns', 'machete_add_admin_id_column' );
+ add_filter( 'manage_users_custom_column', 'machete_render_admin_user_id_column', 10, 3 );
+}
+
+// Hide the admin bar on the frontend for non-administrators.
+if ( in_array( 'disable_admin_bar_frontend', $this->settings, true ) && ! is_admin() ) {
+ add_action(
+ 'after_setup_theme',
+ function () {
+ if ( current_user_can( 'manage_options' ) ) {
+ return;
+ }
+ show_admin_bar( false );
+ }
+ );
+}
--- a/machete/machete-admin.php
+++ b/machete/machete-admin.php
@@ -61,6 +61,19 @@
$machete->notice( sprintf( __( 'You are using Tracking settings from a previous Machete version. Go to the %1$sAnalytics & Code Module page%2$s and <strong>Save Settings</strong> to remove this notice.', 'machete' ), '<a href="' . $module_url . '">', '</a>' ), 'warning', false );
}
}
+ if ( defined( 'MACHETE_POWERTOOLS_INIT' ) ) {
+ $plugins_url = admin_url( 'plugins.php' );
+ $machete->notice(
+ sprintf(
+ /* Translators: 1: link open tag 2: link close tag */
+ __( 'The Machete PowerTools plugin is no longer needed and can be uninstalled. %1$sGo to Plugins%2$s.', 'machete' ),
+ '<a href="' . esc_url( $plugins_url ) . '">',
+ '</a>'
+ ),
+ 'warning',
+ true
+ );
+ }
}
);
@@ -82,16 +95,45 @@
}
);
- // Enqueue admin styles.
+ // Enqueue admin styles and About page scripts.
add_action(
'admin_enqueue_scripts',
- function () {
+ function ( $hook_suffix ) {
wp_enqueue_style(
'machete_admin_4',
plugin_dir_url( __FILE__ ) . 'css/admin.css',
array(),
MACHETE_VERSION
);
+
+ if ( 'toplevel_page_machete' !== $hook_suffix ) {
+ return;
+ }
+
+ wp_enqueue_script(
+ 'machete-module-activate-warning',
+ MACHETE_BASE_URL . 'inc/about/js/module-activate-warning.js',
+ array( 'wp-element', 'wp-components', 'wp-i18n' ),
+ MACHETE_VERSION,
+ true
+ );
+
+ wp_localize_script(
+ 'machete-module-activate-warning',
+ 'macheteAboutWarning',
+ array(
+ 'confirm' => __( 'Activate', 'machete' ),
+ 'cancel' => __( 'Cancel', 'machete' ),
+ )
+ );
+
+ if ( function_exists( 'wp_set_script_translations' ) ) {
+ wp_set_script_translations(
+ 'machete-module-activate-warning',
+ 'machete',
+ MACHETE_BASE_PATH . 'languages'
+ );
+ }
}
);
}
--- a/machete/machete.php
+++ b/machete/machete.php
@@ -3,7 +3,7 @@
* Plugin Name: Machete
* Plugin URI: https://machetewp.com
* Description: Machete is a lean and simple suite of tools that makes WordPress development easier: cookie bar, tracking codes, custom code editor, header cleanup, post and page cloner
- * Version: 5.2
+ * Version: 5.3
* Requires at least: 5.9
* Requires PHP: 7.4
* Author: Nilo Velez
@@ -22,7 +22,7 @@
exit;
}
-define( 'MACHETE_VERSION', '5.2' );
+define( 'MACHETE_VERSION', '5.3' );
$machete_get_upload_dir = wp_upload_dir();
define( 'MACHETE_BASE_PATH', plugin_dir_path( __FILE__ ) );
@@ -57,15 +57,30 @@
require MACHETE_BASE_PATH . 'inc/woocommerce/class-machete-woocommerce-module.php';
require MACHETE_BASE_PATH . 'inc/powertools/class-machete-powertools-module.php';
-// Management of disabled modules.
-$machete_disabled_modules = get_option( 'machete_disabled_modules', array() );
-foreach ( $machete_disabled_modules as $machete_module ) {
- if (
- isset( $machete->modules[ $machete_module ] ) &&
- $machete->modules[ $machete_module ]->params['can_be_disabled']
- ) {
- $machete->modules[ $machete_module ]->params['is_active'] = false;
+// Management of enabled modules.
+if ( null === get_option( 'machete_enabled_modules', null ) ) {
+ $machete_enabled_modules = array();
+ foreach ( $machete->modules as $machete_module ) {
+ if ( ! $machete_module->params['can_be_disabled'] ) {
+ continue;
+ }
+ if ( $machete_module->params['is_active_default'] ) {
+ $machete_enabled_modules[] = $machete_module->params['slug'];
+ }
+ }
+ add_option( 'machete_enabled_modules', $machete_enabled_modules );
+}
+
+$machete_enabled_modules = get_option( 'machete_enabled_modules', array() );
+foreach ( $machete->modules as $machete_module ) {
+ if ( ! $machete_module->params['can_be_disabled'] ) {
+ continue;
}
+ $machete_module->params['is_active'] = in_array(
+ $machete_module->params['slug'],
+ $machete_enabled_modules,
+ true
+ );
}
// Main init.
@@ -74,14 +89,6 @@
function () {
global $machete;
- load_plugin_textdomain( 'machete', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
-
- // Manage of external modules.
- if ( defined( 'MACHETE_POWERTOOLS_INIT' ) ) {
- $machete->modules['powertools']->params['is_active'] = true;
- $machete->modules['powertools']->params['description'] = __( 'Machete PowerTools are now active! Enjoy your new toy!', 'machete' );
- }
-
// Disable WooCommerce module if WooCommece is active.
if ( ! function_exists( 'is_woocommerce' ) ) {
$machete->modules['woocommerce']->params['is_active'] = false;
--- a/machete/uninstall.php
+++ b/machete/uninstall.php
@@ -13,7 +13,7 @@
}
// machete module manager options.
-delete_option( 'machete_disabled_modules' );
+delete_option( 'machete_enabled_modules' );
// machete cleanup options.
delete_option( 'machete_cleanup_settings' );
@@ -32,3 +32,6 @@
// machete WooCommerce options.
delete_option( 'machete_woocommerce_settings' );
+
+// machete powertools options.
+delete_option( 'machete_powertools_settings' );