' . esc_html__('Ensure you are using the correct feed URL', 'activity-log-pro') . '
';
$xml .= '
' . esc_html__('Verify that your access token is valid and has not expired', 'activity-log-pro') . '
';
$xml .= '
' . esc_html__('Check if the feed feature is enabled in the Activity Log settings', 'activity-log-pro') . '
';
$xml .= '
';
$xml .= ']]>' . "\n";
$xml .= '' . gmdate('r') . '' . "\n";
$xml .= '' . esc_url(home_url('?feed=activity-log&error=invalid_token')) . '' . "\n";
$xml .= '' . "\n";
$xml .= '' . "\n";
$xml .= '';
// Output XML with allowed tags for RSS feed format
$allowed_xml_tags = array(
'rss' => array('version' => array(), 'xmlns:atom' => array()),
'channel' => array(),
'title' => array(),
'description' => array(),
'link' => array(),
'item' => array(),
'pubDate' => array(),
'guid' => array('isPermaLink' => array()),
'p' => array(),
'ul' => array(),
'li' => array()
);
echo wp_kses($xml, $allowed_xml_tags);
die();
}
/**
* Validate an RSS feed token
*
* @param string $token The token to validate
* @return bool Whether the token is valid
*/
private function validate_feed_token($token) {
if (empty($token)) {
return false;
}
$stored_token = $this->get_token();
return !empty($stored_token) && hash_equals($stored_token, $token);
}
/**
* Check if RSS feed is enabled
*
* @return bool Whether the RSS feed is enabled
*/
private function is_feed_enabled() {
return get_option('actlogpro_enable_rss_feed', '0') === '1';
}
/**
* Register RSS feed
*/
public function register_rss_feed() {
// Register the feed with WordPress regardless of enabled status
// We'll check the status when generating the feed
add_feed('activity-log', array($this, 'generate_rss_feed'));
}
/**
* Generate RSS feed
*/
public function generate_rss_feed() {
// First, check if RSS feed is enabled at all
$rss_feed_enabled = get_option('actlogpro_enable_rss_feed', '0') === '1';
if (!$rss_feed_enabled) {
$this->generate_invalid_rss_response(__('The RSS feed feature is currently disabled. Please contact your site administrator.', 'activity-log-pro'));
return;
}
// Get the token from the request
$token = get_query_var('actlogpro_token', '');
// Determine access method and validate accordingly
if (!empty($token)) {
// Token-based access - token must be valid
if (!$this->validate_feed_token($token)) {
$this->generate_invalid_rss_response(__('Access Denied: The provided token is invalid or has expired. Please contact your site administrator for a new token.', 'activity-log-pro'));
return;
}
} else {
// Direct feed access without token - only allow for logged-in admins
if (!is_user_logged_in() || !current_user_can('manage_options')) {
$this->generate_invalid_rss_response(__('Access Denied: A valid access token is required to view this feed. Please contact your site administrator for the secure feed URL.', 'activity-log-pro'));
return;
}
}
// Include the database class
require_once plugin_dir_path(__FILE__) . 'class-alp-database.php';
// Get logs using the database class with caching (limit to 50 items)
$logs = ACTLOGPRO_Database::get_feed_logs(50, 0);
// Set the content type
header('Content-Type: application/rss+xml; charset=' . get_option('blog_charset'), true);
// Start output buffering
ob_start();
// Output the RSS header
// Use esc_attr to properly escape the blog charset
echo '';
?>
format_log_for_display($log);
?>
]]>
is_feed_enabled()) {
$this->generate_invalid_rss_response(__('The RSS feed feature is currently disabled. Please contact your site administrator.', 'activity-log-pro'));
return;
}
// Get token from query var
$token = get_query_var('actlogpro_token', '');
// Validate token
if (!$this->validate_feed_token($token)) {
$this->generate_invalid_rss_response(__('Access Denied: The provided token is invalid or has expired. Please contact your site administrator for a new token.', 'activity-log-pro'));
return;
}
// Generate the feed
$this->generate_rss_feed();
}
/**
* Format log data for display
*/
private function format_log_for_display($log) {
// Format user data
$user_data = [];
if (!empty($log['user_id']) && $log['user_id'] != 0) {
// Regular user - get data from WordPress user system
$user = get_userdata($log['user_id']);
if ($user) {
$user_data = [
'id' => $user->ID,
'login' => $user->user_login,
'display_name' => $user->display_name
];
}
} elseif ($log['user_id'] == 0 || !empty($log['user_login'])) {
// System user or user with stored login data - use stored data from log
$user_data = [
'id' => $log['user_id'],
'login' => $log['user_login'] ?? 'system',
'display_name' => $log['user_display_name'] ?? 'System'
];
}
// Format object data
$object_data = [];
if (!empty($log['object_type'])) {
$object_data = [
'type' => $log['object_type'],
'name' => $log['object_name']
];
}
$severity = isset( $log['severity'] ) && ACTLOGPRO_Severity::is_valid( $log['severity'] ) ? $log['severity'] : ACTLOGPRO_Severity::LEVEL_DEFAULT;
return [
'id' => $log['id'],
'created_at' => $log['created_at'],
'user_id' => $log['user_id'],
'user' => $user_data,
'ip_address' => $log['ip_address'],
'event_type' => $log['event_type'],
'object' => $object_data,
'action' => $log['action'],
'description' => $log['description'],
'severity' => $severity,
'severity_label' => ACTLOGPRO_Severity::get_label( $severity ),
];
}
/**
* Register fallback JSON routes when premium is not active
*/
public function register_json_fallback_routes() {
// Check if premium is active by looking for the actlogpro_Feed_JSON class
if (class_exists('ACTLOGPRO_Feed_JSON')) {
// Premium is active, don't register fallback routes
return;
}
// Register fallback routes that return proper error messages
// Main logs endpoint - requires admin access for security
register_rest_route('activity-log-pro/v1', '/logs', array(
'methods' => 'GET',
'callback' => array($this, 'json_fallback_response'),
'permission_callback' => function() {
return current_user_can('manage_options'); // Admin required: Activity logs contain sensitive data
},
'show_in_index' => false
));
// Token-based endpoint - validate token format and check if feature is enabled
register_rest_route('activity-log-pro/v1', '/logs/token/(?P[^/]+)', array(
'methods' => 'GET',
'callback' => array($this, 'json_fallback_response'),
'permission_callback' => array($this, 'check_fallback_token_permission'),
'show_in_index' => false
));
}
/**
* Check permissions for token-based fallback endpoint
* Even though this is a fallback that returns errors, we should validate the request format
*/
public function check_fallback_token_permission($request) {
// Validate that token parameter exists and has reasonable format
$token = $request->get_param('token');
if (empty($token) || !is_string($token) || strlen($token) < 10) {
return new WP_Error(
'invalid_token_format',
__('Invalid token format provided.', 'activity-log-pro'),
array('status' => 400)
);
}
// Allow the request to proceed (it will get the fallback error response)
return true;
}
/**
* Return proper error message for JSON feed requests when premium is not active
*/
public function json_fallback_response($request) {
return new WP_Error(
'invalid_token',
__('Access Denied: The provided token is invalid or has expired. Please contact your site administrator for a new token.', 'activity-log-pro'),
array('status' => 403)
);
}
}