debug_enabled = (defined('WP_DEBUG') && WP_DEBUG);
add_action('plugins_loaded', array($this, 'check_compatibility'), 5);
add_action('admin_init', array($this, 'admin_init'), 5);
}
public function check_compatibility() {
if (version_compare(get_bloginfo('version'), '5.0', '<')) {
add_action('admin_notices', array($this, 'wordpress_version_notice'));
return;
}
if (version_compare(PHP_VERSION, '7.4', '<')) {
add_action('admin_notices', array($this, 'php_version_notice'));
return;
}
if (!function_exists('get_post_types') || !function_exists('get_posts') || !function_exists('wp_create_nonce')) {
add_action('admin_notices', array($this, 'missing_functions_notice'));
return;
}
$this->plugin_ready = true;
if (is_admin()) {
add_action('init', array($this, 'init'), 20);
add_action('wp_ajax_twf_admin_posts_nav_get_posts', array($this, 'ajax_get_nav_posts_with_rate_limit'));
add_action('wp_ajax_twf_admin_posts_nav_update_sort', array($this, 'ajax_update_sort_preference'));
add_action('save_post', array($this, 'clear_navigation_cache'));
add_action('delete_post', array($this, 'clear_navigation_cache'));
add_action('wp_trash_post', array($this, 'clear_navigation_cache'));
add_action('untrash_post', array($this, 'clear_navigation_cache'));
}
}
public function admin_init() {
if (!$this->plugin_ready) {
return;
}
if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) {
return;
}
}
public function init() {
if (!$this->plugin_ready) {
return;
}
if (!is_admin() || (!current_user_can('edit_posts') && !current_user_can('edit_pages'))) {
return;
}
$this->supported_post_types = $this->get_supported_post_types_cached();
if (empty($this->supported_post_types)) {
return;
}
add_action('edit_form_after_title', array($this, 'add_navigation_buttons'));
add_action('enqueue_block_editor_assets', array($this, 'enqueue_gutenberg_assets'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
if ($this->debug_enabled) {
add_action('admin_notices', array($this, 'show_supported_types_notice'));
}
}
/**
* Get user's sort preference for a specific post type
*/
private function get_sort_preference($post_type) {
$user_id = get_current_user_id();
$preference_key = 'twf_admin_posts_nav_sort_' . $post_type;
$preference = get_user_meta($user_id, $preference_key, true);
// Default sort preferences by post type
$defaults = array(
'page' => 'alphabetical',
'post' => 'date',
);
if (empty($preference)) {
$preference = isset($defaults[$post_type]) ? $defaults[$post_type] : 'date';
}
return $preference;
}
/**
* AJAX handler for updating sort preference
*/
public function ajax_update_sort_preference() {
$user_id = get_current_user_id();
// Security: Check user permissions first
if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) {
wp_die(esc_html__('You do not have permission to perform this action.', 'admin-posts-navigation'));
}
// Security: Use check_ajax_referer for AJAX nonce verification
check_ajax_referer('twf_admin_posts_navigation_sort_' . $user_id, 'nonce');
$post_type = sanitize_key($_POST['post_type'] ?? '');
$sort_method = sanitize_key($_POST['sort_method'] ?? '');
if (!$post_type || !in_array($sort_method, array('alphabetical', 'post_id', 'date'))) {
wp_die(esc_html__('Invalid parameters.', 'admin-posts-navigation'));
}
$preference_key = 'twf_admin_posts_nav_sort_' . $post_type;
update_user_meta($user_id, $preference_key, $sort_method);
// Clear navigation cache for this post type
$this->clear_cache_by_post_type($post_type);
wp_send_json_success(array('sort_method' => $sort_method));
}
/**
* Debug logging function that respects WordPress debugging settings
*/
private function debug_log($message, $context = array()) {
if (!$this->debug_enabled) {
return;
}
// No escaping needed for log messages - they're not output to browser
$log_message = 'TWF Admin Posts Navigation: ' . $message;
if (!empty($context)) {
$log_message .= ' Context: ' . wp_json_encode($context);
}
// Only use WordPress debug logging - no error_log()
if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG && function_exists('wp_debug_log')) {
wp_debug_log($log_message);
}
// If wp_debug_log is not available, we simply don't log (fail silently)
// This ensures no error_log() calls that Plugin Check flags
}
/**
* Show admin notice for errors (only to administrators)
*/
private function show_admin_error($message) {
add_action('admin_notices', function() use ($message) {
if (current_user_can('manage_options')) {
printf(
'
',
esc_html__('Admin Posts Navigation', 'admin-posts-navigation'),
esc_html($message)
);
}
});
}
public function wordpress_version_notice() {
echo '';
printf(
'%s: %s',
esc_html__('Admin Posts Navigation', 'admin-posts-navigation'),
esc_html__('This plugin requires WordPress 5.0 or higher.', 'admin-posts-navigation')
);
echo '
';
}
public function php_version_notice() {
echo '';
printf(
'%s: %s',
esc_html__('Admin Posts Navigation', 'admin-posts-navigation'),
esc_html__('This plugin requires PHP 7.4 or higher.', 'admin-posts-navigation')
);
echo '
';
}
public function missing_functions_notice() {
echo '';
printf(
'%s: %s',
esc_html__('Admin Posts Navigation', 'admin-posts-navigation'),
esc_html__('Required WordPress functions are missing. Please check your WordPress installation.', 'admin-posts-navigation')
);
echo '
';
}
private function get_supported_post_types_cached() {
$cache_key = $this->cache_key_prefix . 'supported_types_' . get_current_user_id();
// Use WordPress Object Cache API
$cached_types = wp_cache_get($cache_key, 'twf_admin_posts_navigation');
if ($cached_types !== false && is_array($cached_types)) {
$this->debug_log('Using cached supported post types', array('types' => $cached_types));
return $cached_types;
}
// Fallback to transients if object cache fails
$cached_types = get_transient($cache_key);
if ($cached_types !== false && is_array($cached_types)) {
// Store in object cache for this request
wp_cache_set($cache_key, $cached_types, 'twf_admin_posts_navigation', 300);
$this->debug_log('Using transient cached supported post types', array('types' => $cached_types));
return $cached_types;
}
try {
$supported_types = $this->get_supported_post_types();
// Store in both object cache and transients
wp_cache_set($cache_key, $supported_types, 'twf_admin_posts_navigation', 300);
set_transient($cache_key, $supported_types, $this->cache_expiration);
$this->debug_log('Generated fresh supported post types', array('types' => $supported_types));
return $supported_types;
} catch (Exception $e) {
// Log the detailed error for debugging
$this->debug_log('Error getting supported post types: ' . $e->getMessage());
// Show a safe, escaped admin notice
$this->show_admin_error(__('Unable to detect supported post types. Using defaults.', 'admin-posts-navigation'));
return array('post', 'page');
}
}
private function get_supported_post_types() {
try {
$all_post_types = get_post_types(array('public' => true), 'objects');
if (!is_array($all_post_types)) {
throw new Exception('get_post_types() did not return an array');
}
$supported_types = array();
$excluded_types = apply_filters('twf_admin_posts_navigation_excluded_types', array(
'attachment',
'revision',
'nav_menu_item',
'custom_css',
'customize_changeset',
'oembed_cache',
'user_request',
'wp_block',
'wp_template',
'wp_template_part',
'wp_global_styles',
'wp_navigation'
));
foreach ($all_post_types as $post_type) {
if (!$post_type || !is_object($post_type) || !isset($post_type->name)) {
continue;
}
if (!in_array($post_type->name, $excluded_types) &&
isset($post_type->cap->edit_posts) &&
current_user_can($post_type->cap->edit_posts)) {
$supported_types[] = sanitize_key($post_type->name);
}
}
// Ensure core post types are included if they exist and user has permission
if (post_type_exists('post') && current_user_can('edit_posts') && !in_array('post', $supported_types)) {
$supported_types[] = 'post';
}
if (post_type_exists('page') && current_user_can('edit_pages') && !in_array('page', $supported_types)) {
$supported_types[] = 'page';
}
return array_unique($supported_types);
} catch (Exception $e) {
// Log the original error for debugging
$this->debug_log('Failed to get supported post types: ' . $e->getMessage());
// Don't re-throw with the original message, use a safe generic message
throw new Exception('Failed to get supported post types');
}
}
public function add_navigation_buttons() {
global $post;
if (!$post || !is_object($post) || !isset($post->post_type) || !isset($post->ID)) {
return;
}
if (!in_array($post->post_type, $this->supported_post_types)) {
return;
}
if (!current_user_can('edit_post', $post->ID)) {
return;
}
try {
$nav_data = $this->get_navigation_data_cached($post);
if (!$nav_data || (!$nav_data['prev'] && !$nav_data['next'])) {
return;
}
$post_type_obj = get_post_type_object($post->post_type);
$post_type_label = $post_type_obj && isset($post_type_obj->labels->singular_name)
? $post_type_obj->labels->singular_name
: ucfirst($post->post_type);
$current_sort = $this->get_sort_preference($post->post_type);
echo '';
// Header with dropdown
echo '';
// Navigation buttons
echo '
';
if ($nav_data['prev'] && is_object($nav_data['prev'])) {
printf(
'
← %s: %s',
esc_url(get_edit_post_link($nav_data['prev']->ID)),
esc_attr($nav_data['prev']->post_title),
absint($nav_data['prev']->ID),
esc_html__('Previous', 'admin-posts-navigation'),
esc_html(wp_trim_words($nav_data['prev']->post_title, 5))
);
}
if ($nav_data['next'] && is_object($nav_data['next'])) {
printf(
'
%s: %s →',
esc_url(get_edit_post_link($nav_data['next']->ID)),
esc_attr($nav_data['next']->post_title),
absint($nav_data['next']->ID),
esc_html__('Next', 'admin-posts-navigation'),
esc_html(wp_trim_words($nav_data['next']->post_title, 5))
);
}
echo '
';
if (isset($nav_data['current_position']) && isset($nav_data['total_posts'])) {
echo '
';
printf(
'%s',
esc_html(sprintf(
// translators: %1$d is current position number, %2$d is total posts, %3$s is post type name
__('Position: %1$d of %2$d %3$s', 'admin-posts-navigation'),
$nav_data['current_position'],
$nav_data['total_posts'],
strtolower($post_type_obj && isset($post_type_obj->labels->name) ? $post_type_obj->labels->name : $post->post_type)
))
);
echo '
';
}
echo '
';
} catch (Exception $e) {
// Log the detailed error for debugging (not output to browser)
$this->debug_log('Error in add_navigation_buttons: ' . $e->getMessage(), array('post_id' => $post->ID));
// Fail silently for users, log for debugging
}
}
public function enqueue_gutenberg_assets() {
global $post;
if (!$post || !is_object($post) || !isset($post->post_type) || !isset($post->ID)) {
return;
}
if (!in_array($post->post_type, $this->supported_post_types)) {
return;
}
if (!current_user_can('edit_post', $post->ID)) {
return;
}
wp_enqueue_script(
'twf-admin-posts-navigation-gutenberg',
plugin_dir_url(__FILE__) . 'assets/gutenberg.js',
array('wp-element', 'wp-components', 'wp-data', 'wp-plugins', 'wp-editor', 'wp-edit-post'),
$this->version,
true
);
$post_type_obj = get_post_type_object($post->post_type);
$current_sort = $this->get_sort_preference($post->post_type);
wp_localize_script('twf-admin-posts-navigation-gutenberg', 'twfAdminPostsNavigation', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('twf_admin_posts_navigation_' . get_current_user_id() . '_' . $post->ID),
'sortNonce' => wp_create_nonce('twf_admin_posts_navigation_sort_' . get_current_user_id()),
'postId' => absint($post->ID),
'postType' => sanitize_key($post->post_type),
'postTypeLabel' => $post_type_obj && isset($post_type_obj->labels->singular_name)
? esc_js($post_type_obj->labels->singular_name)
: esc_js(ucfirst($post->post_type)),
'postTypeLabelPlural' => $post_type_obj && isset($post_type_obj->labels->name)
? esc_js($post_type_obj->labels->name)
: esc_js(ucfirst($post->post_type) . 's'),
'currentSort' => $current_sort,
'sortOptions' => array(
'date' => __('Date Published', 'admin-posts-navigation'),
'alphabetical' => __('Alphabetical', 'admin-posts-navigation'),
'post_id' => __('Post ID', 'admin-posts-navigation')
)
));
}
public function enqueue_admin_assets($hook) {
if (!in_array($hook, array('post.php', 'post-new.php'))) {
return;
}
wp_enqueue_style(
'twf-admin-posts-navigation-styles',
plugin_dir_url(__FILE__) . 'assets/admin-styles.css',
array(),
$this->version
);
// Add inline script for Classic Editor dropdown functionality
wp_add_inline_script('jquery', '
jQuery(document).ready(function($) {
$(document).on("change", ".twf-admin-posts-nav-sort-dropdown", function() {
var $dropdown = $(this);
var postType = $dropdown.data("post-type");
var sortMethod = $dropdown.val();
var nonce = $dropdown.data("nonce");
if (!postType || !sortMethod || !nonce) return;
$dropdown.prop("disabled", true);
$.post(ajaxurl, {
action: "twf_admin_posts_nav_update_sort",
post_type: postType,
sort_method: sortMethod,
nonce: nonce
})
.done(function(response) {
if (response.success) {
// Reload the page to reflect new sort order
location.reload();
}
})
.fail(function() {
alert("Failed to update sort preference");
})
.always(function() {
$dropdown.prop("disabled", false);
});
});
});
');
}
public function ajax_get_nav_posts_with_rate_limit() {
try {
$user_id = get_current_user_id();
// Security: Check user permissions first
if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) {
wp_die(esc_html__('You do not have permission to perform this action.', 'admin-posts-navigation'));
}
$rate_limit_key = 'twf_admin_posts_navigation_rate_limit_' . $user_id . '_' . wp_hash(wp_get_session_token());
// Check rate limit using object cache first
$requests = wp_cache_get($rate_limit_key, 'twf_admin_posts_navigation_rate');
if ($requests === false) {
// Fallback to transients
$requests = get_transient($rate_limit_key);
}
if ($requests && $requests >= 30) {
wp_die(esc_html__('Rate limit exceeded. Please wait before making another request.', 'admin-posts-navigation'));
}
$new_count = $requests ? $requests + 1 : 1;
wp_cache_set($rate_limit_key, $new_count, 'twf_admin_posts_navigation_rate', 60);
set_transient($rate_limit_key, $new_count, 60);
$post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
if (!$post_id) {
wp_die(esc_html__('Invalid post ID.', 'admin-posts-navigation'));
}
// Security: Use check_ajax_referer for AJAX nonce verification
$expected_nonce_action = 'twf_admin_posts_navigation_' . $user_id . '_' . $post_id;
check_ajax_referer($expected_nonce_action, 'nonce');
$post = get_post($post_id);
if (!$post || !is_object($post) || !isset($post->post_type)) {
wp_die(esc_html__('Invalid post.', 'admin-posts-navigation'));
}
if (!in_array($post->post_type, $this->supported_post_types)) {
wp_die(esc_html__('Unsupported post type.', 'admin-posts-navigation'));
}
// Security: Check user can edit this specific post
if (!current_user_can('edit_post', $post_id)) {
wp_die(esc_html__('You do not have permission to edit this post.', 'admin-posts-navigation'));
}
$nav_data = $this->get_navigation_data_cached($post);
$response = array(
'prev' => null,
'next' => null,
'current_position' => isset($nav_data['current_position']) ? absint($nav_data['current_position']) : 0,
'total_posts' => isset($nav_data['total_posts']) ? absint($nav_data['total_posts']) : 0
);
if ($nav_data && isset($nav_data['prev']) && is_object($nav_data['prev'])) {
$response['prev'] = array(
'id' => absint($nav_data['prev']->ID),
'title' => sanitize_text_field($nav_data['prev']->post_title),
'edit_link' => esc_url_raw(get_edit_post_link($nav_data['prev']->ID))
);
}
if ($nav_data && isset($nav_data['next']) && is_object($nav_data['next'])) {
$response['next'] = array(
'id' => absint($nav_data['next']->ID),
'title' => sanitize_text_field($nav_data['next']->post_title),
'edit_link' => esc_url_raw(get_edit_post_link($nav_data['next']->ID))
);
}
wp_send_json_success($response);
} catch (Exception $e) {
// Log the detailed error for debugging (not output to browser)
$this->debug_log('AJAX error: ' . $e->getMessage(), array('post_id' => $post_id ?? 0));
// Use a safe, generic error message for user output
wp_die(esc_html__('Navigation data could not be loaded.', 'admin-posts-navigation'));
}
}
private function get_navigation_data_cached($current_post) {
if (!$current_post || !is_object($current_post) || !isset($current_post->ID)) {
return null;
}
$current_sort = $this->get_sort_preference($current_post->post_type);
$cache_key = $this->cache_key_prefix . 'nav_data_' . $current_post->ID . '_' . $current_post->post_type . '_' . $current_sort;
// Try object cache first
$cached_data = wp_cache_get($cache_key, 'twf_admin_posts_navigation');
if ($cached_data !== false && is_array($cached_data)) {
$this->debug_log('Using cached navigation data', array('post_id' => $current_post->ID));
return $cached_data;
}
// Fallback to transients
$cached_data = get_transient($cache_key);
if ($cached_data !== false && is_array($cached_data)) {
// Store in object cache for this request
wp_cache_set($cache_key, $cached_data, 'twf_admin_posts_navigation', 900);
$this->debug_log('Using transient cached navigation data', array('post_id' => $current_post->ID));
return $cached_data;
}
try {
$nav_data = $this->get_navigation_data($current_post);
if ($nav_data) {
// Store in both caches
wp_cache_set($cache_key, $nav_data, 'twf_admin_posts_navigation', 900);
set_transient($cache_key, $nav_data, 1800);
$this->debug_log('Generated fresh navigation data', array('post_id' => $current_post->ID));
}
return $nav_data;
} catch (Exception $e) {
// Log the detailed error for debugging (not output to browser)
$this->debug_log('Error getting navigation data: ' . $e->getMessage(), array('post_id' => $current_post->ID));
return null;
}
}
private function get_navigation_data($current_post) {
try {
if (!$current_post || !is_object($current_post) || !isset($current_post->ID) || !isset($current_post->post_type)) {
throw new Exception('Invalid post object');
}
$sort_preference = $this->get_sort_preference($current_post->post_type);
// Set orderby and order based on sort preference
switch ($sort_preference) {
case 'alphabetical':
$orderby = 'title';
$order = 'ASC';
break;
case 'post_id':
$orderby = 'ID';
$order = 'ASC';
break;
case 'date':
default:
$orderby = 'date';
$order = 'DESC';
// Special handling for pages - use menu_order if available
if ($current_post->post_type === 'page') {
$orderby = 'menu_order date';
$order = 'ASC';
}
break;
}
$orderby = apply_filters('twf_admin_posts_navigation_orderby', $orderby, $current_post->post_type);
$order = apply_filters('twf_admin_posts_navigation_order', $order, $current_post->post_type);
// Use WordPress cache-friendly query
$args = array(
'post_type' => sanitize_key($current_post->post_type),
'post_status' => array('publish', 'draft', 'pending', 'private', 'future'),
'posts_per_page' => 500,
'orderby' => sanitize_text_field($orderby),
'order' => sanitize_text_field($order),
'fields' => 'ids',
'suppress_filters' => false,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'cache_results' => true, // Explicitly enable caching
);
$args = apply_filters('twf_admin_posts_navigation_query_args', $args, $current_post);
// Use get_posts which properly handles caching
$all_posts = get_posts($args);
if (!is_array($all_posts)) {
throw new Exception('get_posts() did not return an array');
}
$current_index = array_search($current_post->ID, $all_posts);
$prev_post = null;
$next_post = null;
$current_position = 0;
$total_posts = count($all_posts);
if ($current_index !== false) {
$current_position = $current_index + 1;
if ($current_index > 0) {
$prev_post_id = $all_posts[$current_index - 1];
$prev_post = get_post($prev_post_id);
if (!$prev_post) {
$this->debug_log('Could not get previous post', array('post_id' => $prev_post_id));
}
}
if ($current_index < count($all_posts) - 1) {
$next_post_id = $all_posts[$current_index + 1];
$next_post = get_post($next_post_id);
if (!$next_post) {
$this->debug_log('Could not get next post', array('post_id' => $next_post_id));
}
}
}
return array(
'prev' => $prev_post,
'next' => $next_post,
'current_position' => $current_position,
'total_posts' => $total_posts
);
} catch (Exception $e) {
// Log the original error for debugging
$this->debug_log('Navigation data generation failed: ' . $e->getMessage());
// Don't re-throw with the original message, use a safe generic message
throw new Exception('Navigation data generation failed');
}
}
public function clear_navigation_cache($post_id = null) {
if (!$post_id) {
return;
}
try {
$post = get_post($post_id);
if (!$post || !isset($post->post_type)) {
return;
}
// Clear object cache and transients for this post type
$this->clear_cache_by_post_type($post->post_type);
// Clear user-specific caches
$user_cache_key = $this->cache_key_prefix . 'supported_types_' . get_current_user_id();
wp_cache_delete($user_cache_key, 'twf_admin_posts_navigation');
delete_transient($user_cache_key);
$this->debug_log('Cleared navigation cache', array('post_id' => $post_id, 'post_type' => $post->post_type));
} catch (Exception $e) {
// Log the detailed error for debugging (not output to browser)
$this->debug_log('Error clearing cache: ' . $e->getMessage(), array('post_id' => $post_id));
}
}
private function clear_cache_by_post_type($post_type) {
// Note: WordPress object cache doesn't support pattern-based deletion
// In a production environment, you'd want to implement cache groups
// For now, we clear known cache keys for all sort preferences
$cache_keys_to_clear = array(
$this->cache_key_prefix . 'supported_types_' . get_current_user_id(),
);
foreach ($cache_keys_to_clear as $key) {
wp_cache_delete($key, 'twf_admin_posts_navigation');
delete_transient($key);
}
// Clear rate limiting cache
$user_id = get_current_user_id();
$rate_limit_key = 'twf_admin_posts_navigation_rate_limit_' . $user_id . '_' . wp_hash(wp_get_session_token());
wp_cache_delete($rate_limit_key, 'twf_admin_posts_navigation_rate');
delete_transient($rate_limit_key);
}
public function show_supported_types_notice() {
$screen = get_current_screen();
if (!$screen || !in_array($screen->id, array('edit-post', 'edit-page')) || !current_user_can('manage_options')) {
return;
}
$notice_key = 'twf_admin_posts_navigation_notice_' . get_current_user_id() . '_' . wp_hash(wp_get_session_token());
if (get_transient($notice_key)) {
return;
}
set_transient($notice_key, true, 3600);
echo '';
printf(
'
%s: %s
',
esc_html__('Admin Posts Navigation', 'admin-posts-navigation'),
esc_html(sprintf(
// translators: %1$d is the number of post types, %2$s is the list of post type names
__('Active for %1$d post types: %2$s', 'admin-posts-navigation'),
count($this->supported_post_types),
implode(', ', array_map('ucfirst', $this->supported_post_types))
))
);
echo '
';
}
public function get_supported_types() {
return $this->plugin_ready ? $this->supported_post_types : array();
}
}
// Initialize the plugin
if (class_exists('TWF_AdminPostsNavigation')) {
$twf_admin_posts_navigation = new TWF_AdminPostsNavigation();
}
// Activation hook
function twf_admin_posts_navigation_activation_hook() {
add_option('twf_admin_posts_navigation_activated', true);
}
register_activation_hook(__FILE__, 'twf_admin_posts_navigation_activation_hook');
// Deactivation hook
function twf_admin_posts_navigation_deactivation_hook() {
// Clear all caches on deactivation
$user_cache_key = 'twf_admin_posts_navigation_supported_types_' . get_current_user_id();
wp_cache_delete($user_cache_key, 'twf_admin_posts_navigation');
delete_transient($user_cache_key);
}
register_deactivation_hook(__FILE__, 'twf_admin_posts_navigation_deactivation_hook');
// Uninstall hook
function twf_admin_posts_navigation_uninstall_hook() {
// Clean up all plugin data on uninstall
$user_cache_key = 'twf_admin_posts_navigation_supported_types_' . get_current_user_id();
wp_cache_delete($user_cache_key, 'twf_admin_posts_navigation');
delete_transient($user_cache_key);
delete_option('twf_admin_posts_navigation_activated');
// Clean up user sort preferences using WordPress functions only
// Get all users to clean up their sort preferences
$users = get_users(array('fields' => 'ID'));
if (!empty($users)) {
foreach ($users as $user_id) {
// Get all user meta for this user
$user_meta = get_user_meta($user_id);
// Loop through meta keys and delete any that match our pattern
if (is_array($user_meta)) {
foreach ($user_meta as $meta_key => $meta_value) {
if (strpos($meta_key, 'twf_admin_posts_nav_sort_') === 0) {
delete_user_meta($user_id, $meta_key);
}
}
}
}
}
}
register_uninstall_hook(__FILE__, 'twf_admin_posts_navigation_uninstall_hook');
// Plugin action links
function twf_admin_posts_navigation_plugin_action_links($links) {
try {
global $twf_admin_posts_navigation;
if ($twf_admin_posts_navigation && method_exists($twf_admin_posts_navigation, 'get_supported_types')) {
$supported_types = $twf_admin_posts_navigation->get_supported_types();
if (is_array($supported_types) && !empty($supported_types)) {
$settings_link = '' .
esc_html(sprintf(
// translators: %s is a list of supported post types
__('Supports: %s', 'admin-posts-navigation'),
implode(', ', array_slice($supported_types, 0, 3)) .
(count($supported_types) > 3 ? '... (+' . (count($supported_types) - 3) . ' more)' : '')
)) .
'';
array_unshift($links, $settings_link);
}
}
} catch (Exception $e) {
// Silently fail - plugin links are not critical functionality
if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
// Only use WordPress debug logging - no error_log()
if (function_exists('wp_debug_log')) {
wp_debug_log('TWF Admin Posts Navigation: Plugin action links error - ' . $e->getMessage());
}
// If wp_debug_log is not available, we simply don't log (fail silently)
// This ensures no error_log() calls that Plugin Check flags
}
}
return $links;
}
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'twf_admin_posts_navigation_plugin_action_links');