518 lines
16 KiB
PHP
518 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: antisocial-safety
|
|
* Description: Secures attachment uploads and comments with the OpenAI moderation endpoint
|
|
* Version: 1.7
|
|
*/
|
|
|
|
|
|
// Prevent direct access to the file
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Global variable to store moderation results temporarily
|
|
*/
|
|
global $oai_moderation_results;
|
|
$oai_moderation_results = array();
|
|
|
|
/**
|
|
* Attachment Moderation
|
|
*/
|
|
|
|
// Hook into 'add_attachment' to moderate attachments upon upload
|
|
add_action('add_attachment', 'oai_moderate_attachment');
|
|
|
|
function oai_moderate_attachment($post_ID) {
|
|
$attachment = get_post($post_ID);
|
|
$mime_type = get_post_mime_type($post_ID);
|
|
|
|
// Only moderate images
|
|
if (strpos($mime_type, 'image/') !== false) {
|
|
// Get the API key
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
// API key not set; skip moderation
|
|
return;
|
|
}
|
|
|
|
// Get the file URL
|
|
$file_url = wp_get_attachment_url($post_ID);
|
|
|
|
// Moderate the image
|
|
$result = oai_moderate_image($file_url);
|
|
|
|
if (isset($result['error'])) {
|
|
// Handle error (optional: log the error)
|
|
// For graceful failure, do not flag the attachment
|
|
return;
|
|
}
|
|
|
|
// Save the moderation result
|
|
update_post_meta($post_ID, '_oai_moderation_result', $result);
|
|
|
|
if ($result['flagged']) {
|
|
// Mark as flagged
|
|
update_post_meta($post_ID, '_oai_moderation_flagged', true);
|
|
|
|
// Delete the flagged attachment
|
|
wp_delete_attachment($post_ID, true);
|
|
|
|
// Set a transient to show an admin notice
|
|
set_transient('oai_flagged_upload_notice', __('An uploaded image was flagged by OpenAI Moderation and has been removed.', 'antisocial-safety'), 10);
|
|
|
|
// Optionally, you can log this event or take other actions
|
|
}
|
|
}
|
|
}
|
|
|
|
function oai_moderate_image($image_url) {
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
// API key not set; skip moderation
|
|
return array();
|
|
}
|
|
|
|
$endpoint = 'https://api.openai.com/v1/moderations';
|
|
|
|
$data = array(
|
|
'model' => 'omni-moderation-latest',
|
|
'input' => array(
|
|
array(
|
|
'type' => 'image_url',
|
|
'image_url' => array('url' => $image_url),
|
|
),
|
|
),
|
|
);
|
|
|
|
$args = array(
|
|
'headers' => array(
|
|
'Content-Type' => 'application/json',
|
|
'Authorization' => 'Bearer ' . $api_key,
|
|
),
|
|
'body' => wp_json_encode($data),
|
|
'timeout' => 60,
|
|
);
|
|
|
|
$response = wp_remote_post($endpoint, $args);
|
|
|
|
if (is_wp_error($response)) {
|
|
// For graceful failure, return an empty array
|
|
return array('error' => $response->get_error_message());
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$result = json_decode($body, true);
|
|
|
|
if (isset($result['results'][0])) {
|
|
return $result['results'][0];
|
|
} else {
|
|
// Invalid response; return an empty array
|
|
return array();
|
|
}
|
|
}
|
|
|
|
// Display admin notice if an upload was flagged and removed
|
|
add_action('admin_notices', 'oai_flagged_upload_admin_notice');
|
|
|
|
function oai_flagged_upload_admin_notice() {
|
|
if ($message = get_transient('oai_flagged_upload_notice')) {
|
|
echo '<div class="notice notice-warning is-dismissible">
|
|
<p>' . esc_html($message) . '</p>
|
|
</div>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Comments Moderation
|
|
*/
|
|
|
|
// Moderate comments before saving
|
|
add_filter('preprocess_comment', 'oai_moderate_comment');
|
|
|
|
function oai_moderate_comment($commentdata) {
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
return $commentdata; // If API key not set, let the comment pass
|
|
}
|
|
|
|
$comment_content = $commentdata['comment_content'];
|
|
|
|
// Moderate the comment text
|
|
$result = oai_moderate_text($comment_content);
|
|
|
|
if (isset($result['error'])) {
|
|
// Handle error (optional: log the error)
|
|
// For graceful failure, let the comment pass
|
|
return $commentdata;
|
|
}
|
|
|
|
// Store the moderation result in a global variable indexed by comment content hash
|
|
$hash = md5($comment_content);
|
|
global $oai_moderation_results;
|
|
$oai_moderation_results[$hash] = $result;
|
|
|
|
return $commentdata;
|
|
}
|
|
|
|
// Set the comment approval status based on moderation result
|
|
add_filter('pre_comment_approved', 'oai_set_comment_approval_status', 10, 2);
|
|
|
|
function oai_set_comment_approval_status($approved, $commentdata) {
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
return $approved; // Let the comment pass if API key not set
|
|
}
|
|
|
|
$comment_content = $commentdata['comment_content'];
|
|
$hash = md5($comment_content);
|
|
|
|
global $oai_moderation_results;
|
|
if (isset($oai_moderation_results[$hash])) {
|
|
$result = $oai_moderation_results[$hash];
|
|
if ($result['flagged']) {
|
|
// Set comment to unapproved
|
|
return '0'; // Unapproved
|
|
}
|
|
}
|
|
return $approved;
|
|
}
|
|
|
|
// Save the moderation result after the comment is inserted
|
|
add_action('comment_post', 'oai_save_comment_moderation_meta', 10, 2);
|
|
|
|
function oai_save_comment_moderation_meta($comment_ID, $comment_approved) {
|
|
$comment = get_comment($comment_ID);
|
|
$comment_content = $comment->comment_content;
|
|
$hash = md5($comment_content);
|
|
|
|
global $oai_moderation_results;
|
|
if (isset($oai_moderation_results[$hash])) {
|
|
$result = $oai_moderation_results[$hash];
|
|
update_comment_meta($comment_ID, '_oai_moderation_result', $result);
|
|
update_comment_meta($comment_ID, '_oai_moderation_flagged', $result['flagged'] ? 'pending' : 'ok');
|
|
// Remove from global variable
|
|
unset($oai_moderation_results[$hash]);
|
|
}
|
|
}
|
|
|
|
function oai_moderate_text($text) {
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
// API key not set; skip moderation
|
|
return array(); // Return an empty array indicating no moderation
|
|
}
|
|
|
|
$endpoint = 'https://api.openai.com/v1/moderations';
|
|
|
|
$data = array(
|
|
'model' => 'omni-moderation-latest',
|
|
'input' => array(
|
|
array(
|
|
'type' => 'text',
|
|
'text' => $text,
|
|
),
|
|
),
|
|
);
|
|
|
|
$args = array(
|
|
'headers' => array(
|
|
'Content-Type' => 'application/json',
|
|
'Authorization' => 'Bearer ' . $api_key,
|
|
),
|
|
'body' => wp_json_encode($data),
|
|
'timeout' => 60,
|
|
);
|
|
|
|
$response = wp_remote_post($endpoint, $args);
|
|
|
|
if (is_wp_error($response)) {
|
|
// For graceful failure, return an empty array
|
|
return array('error' => $response->get_error_message());
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$result = json_decode($body, true);
|
|
|
|
if (isset($result['results'][0])) {
|
|
return $result['results'][0];
|
|
} else {
|
|
// Invalid response; return an empty array
|
|
return array();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anonymizer Functionality
|
|
*/
|
|
|
|
// Hook into 'wp_handle_upload_prefilter' to anonymize filenames if enabled
|
|
add_filter('wp_handle_upload_prefilter', 'oai_anonymize_filename');
|
|
|
|
function oai_anonymize_filename($file) {
|
|
// Check if anonymizer is enabled
|
|
$anonymizer_enabled = get_option('oai_enable_anonymizer', false);
|
|
if (!$anonymizer_enabled) {
|
|
return $file;
|
|
}
|
|
|
|
// Get the file extension
|
|
$file_info = pathinfo($file['name']);
|
|
$extension = isset($file_info['extension']) ? '.' . strtolower($file_info['extension']) : '';
|
|
|
|
// Generate a unique 10-digit filename
|
|
$new_filename = oai_generate_unique_filename($extension);
|
|
|
|
// Replace the original filename with the new anonymized filename
|
|
$file['name'] = $new_filename;
|
|
|
|
return $file;
|
|
}
|
|
|
|
function oai_generate_unique_filename($extension) {
|
|
$upload_dir = wp_upload_dir();
|
|
$base_dir = trailingslashit($upload_dir['basedir']);
|
|
|
|
do {
|
|
// Generate a random number between 0000000000 and 9999999999
|
|
$random_number = str_pad(mt_rand(0, 9999999999), 10, '0', STR_PAD_LEFT);
|
|
$new_filename = $random_number . $extension;
|
|
$file_path = $base_dir . $new_filename;
|
|
} while (file_exists($file_path));
|
|
|
|
return $new_filename;
|
|
}
|
|
|
|
/**
|
|
* Admin Columns for Media and Comments
|
|
*/
|
|
|
|
// Add a custom column to the media library
|
|
add_filter('manage_media_columns', 'oai_add_media_column');
|
|
function oai_add_media_column($columns) {
|
|
$columns['oai_moderation'] = __('Moderation', 'antisocial-safety');
|
|
return $columns;
|
|
}
|
|
|
|
add_action('manage_media_custom_column', 'oai_media_column_content', 10, 2);
|
|
function oai_media_column_content($column_name, $post_ID) {
|
|
if ($column_name == 'oai_moderation') {
|
|
$moderation_result = get_post_meta($post_ID, '_oai_moderation_result', true);
|
|
$flagged = get_post_meta($post_ID, '_oai_moderation_flagged', true);
|
|
if ($flagged) {
|
|
echo '<span style="color:red;">' . __('Flagged', 'antisocial-safety') . '</span>';
|
|
} else {
|
|
echo '<span style="color:green;">' . __('OK', 'antisocial-safety') . '</span>';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a custom column to the comments list
|
|
add_filter('manage_edit-comments_columns', 'oai_add_comments_column');
|
|
function oai_add_comments_column($columns) {
|
|
$columns['oai_moderation'] = __('Moderation', 'antisocial-safety');
|
|
return $columns;
|
|
}
|
|
|
|
add_action('manage_comments_custom_column', 'oai_comments_column_content', 10, 2);
|
|
function oai_comments_column_content($column_name, $comment_ID) {
|
|
if ($column_name == 'oai_moderation') {
|
|
$flagged = get_comment_meta($comment_ID, '_oai_moderation_flagged', true);
|
|
if ($flagged === 'pending') {
|
|
echo '<span style="color:orange;">' . __('Pending Approval', 'antisocial-safety') . '</span>';
|
|
} elseif ($flagged === 'flagged') {
|
|
echo '<span style="color:red;">' . __('Flagged', 'antisocial-safety') . '</span>';
|
|
} else {
|
|
echo '<span style="color:green;">' . __('OK', 'antisocial-safety') . '</span>';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display Moderation Details in Admin Screens
|
|
*/
|
|
|
|
// Add moderation info to the attachment edit screen
|
|
add_filter('attachment_fields_to_edit', 'oai_attachment_fields', 10, 2);
|
|
function oai_attachment_fields($form_fields, $post) {
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
// API key not set; do not display moderation fields
|
|
return $form_fields;
|
|
}
|
|
|
|
$flagged = get_post_meta($post->ID, '_oai_moderation_flagged', true);
|
|
$moderation_result = get_post_meta($post->ID, '_oai_moderation_result', true);
|
|
|
|
if ($flagged) {
|
|
$form_fields['oai_moderation'] = array(
|
|
'label' => __('OpenAI Moderation', 'antisocial-safety'),
|
|
'input' => 'html',
|
|
'html' => '<span style="color:red;">' . __('This attachment was flagged by OpenAI Moderation and has been removed.', 'antisocial-safety') . '</span>',
|
|
'helps' => __('Flagged content is blocked from being served to users.', 'antisocial-safety'),
|
|
);
|
|
} else {
|
|
$form_fields['oai_moderation'] = array(
|
|
'label' => __('OpenAI Moderation', 'antisocial-safety'),
|
|
'input' => 'html',
|
|
'html' => '<span style="color:green;">' . __('This attachment passed OpenAI Moderation.', 'antisocial-safety') . '</span>',
|
|
);
|
|
}
|
|
|
|
if ($moderation_result) {
|
|
$form_fields['oai_moderation_result'] = array(
|
|
'label' => __('Moderation Details', 'antisocial-safety'),
|
|
'input' => 'html',
|
|
'html' => '<pre>' . esc_html(print_r($moderation_result, true)) . '</pre>',
|
|
);
|
|
}
|
|
|
|
return $form_fields;
|
|
}
|
|
|
|
// Add a meta box to the comment edit screen for moderation details
|
|
add_action('add_meta_boxes_comment', 'oai_add_comment_meta_box');
|
|
function oai_add_comment_meta_box() {
|
|
add_meta_box(
|
|
'oai_comment_moderation',
|
|
__('OpenAI Moderation Details', 'antisocial-safety'),
|
|
'oai_comment_moderation_meta_box',
|
|
'comment',
|
|
'normal',
|
|
'high'
|
|
);
|
|
}
|
|
|
|
function oai_comment_moderation_meta_box($comment) {
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
// API key not set; do not display moderation details
|
|
return;
|
|
}
|
|
|
|
$moderation_result = get_comment_meta($comment->comment_ID, '_oai_moderation_result', true);
|
|
$flagged = get_comment_meta($comment->comment_ID, '_oai_moderation_flagged', true);
|
|
|
|
if ($flagged) {
|
|
echo '<p><strong>' . __('Status:', 'antisocial-safety') . '</strong> ';
|
|
if ($flagged === 'pending') {
|
|
echo '<span style="color:orange;">' . __('Pending Approval', 'antisocial-safety') . '</span>';
|
|
} elseif ($flagged === 'flagged') {
|
|
echo '<span style="color:red;">' . __('Flagged', 'antisocial-safety') . '</span>';
|
|
}
|
|
echo '</p>';
|
|
|
|
if ($moderation_result) {
|
|
echo '<h4>' . __('Moderation Details', 'antisocial-safety') . '</h4>';
|
|
echo '<pre>' . esc_html(print_r($moderation_result, true)) . '</pre>';
|
|
}
|
|
} else {
|
|
echo '<p><span style="color:green;">' . __('This comment passed OpenAI Moderation.', 'antisocial-safety') . '</span></p>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Settings Page
|
|
*/
|
|
|
|
// Add settings page under Settings menu
|
|
add_action('admin_menu', 'oai_add_admin_menu');
|
|
|
|
function oai_add_admin_menu() {
|
|
add_options_page(
|
|
__('OpenAI Moderation', 'antisocial-safety'),
|
|
__('OpenAI Moderation', 'antisocial-safety'),
|
|
'manage_options',
|
|
'openai-moderation',
|
|
'oai_options_page'
|
|
);
|
|
}
|
|
|
|
function oai_options_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1><?php esc_html_e('OpenAI Moderation Settings', 'antisocial-safety'); ?></h1>
|
|
<form method="post" action="options.php">
|
|
<?php
|
|
settings_fields('oai_options_group');
|
|
do_settings_sections('openai-moderation');
|
|
submit_button();
|
|
?>
|
|
</form>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
// Initialize settings
|
|
add_action('admin_init', 'oai_settings_init');
|
|
|
|
function oai_settings_init() {
|
|
register_setting('oai_options_group', 'oai_api_key');
|
|
register_setting('oai_options_group', 'oai_enable_anonymizer');
|
|
|
|
add_settings_section(
|
|
'oai_settings_section',
|
|
__('OpenAI API Settings', 'antisocial-safety'),
|
|
'oai_settings_section_callback',
|
|
'openai-moderation'
|
|
);
|
|
|
|
add_settings_field(
|
|
'oai_api_key_field',
|
|
__('OpenAI API Key', 'antisocial-safety'),
|
|
'oai_api_key_render',
|
|
'openai-moderation',
|
|
'oai_settings_section'
|
|
);
|
|
|
|
add_settings_field(
|
|
'oai_enable_anonymizer_field',
|
|
__('Enable Filename Anonymizer', 'antisocial-safety'),
|
|
'oai_enable_anonymizer_render',
|
|
'openai-moderation',
|
|
'oai_settings_section'
|
|
);
|
|
}
|
|
|
|
function oai_settings_section_callback() {
|
|
echo '<p>' . esc_html__('Enter your OpenAI API key to enable moderation. You can also toggle the filename anonymizer below.', 'antisocial-safety') . '</p>';
|
|
}
|
|
|
|
function oai_api_key_render() {
|
|
$api_key = get_option('oai_api_key');
|
|
?>
|
|
<input type="text" name="oai_api_key" value="<?php echo esc_attr($api_key); ?>" size="50">
|
|
<?php
|
|
}
|
|
|
|
function oai_enable_anonymizer_render() {
|
|
$enabled = get_option('oai_enable_anonymizer', false);
|
|
?>
|
|
<input type="checkbox" name="oai_enable_anonymizer" value="1" <?php checked(1, $enabled, true); ?> />
|
|
<label for="oai_enable_anonymizer"><?php esc_html_e('Rename uploaded files to unique 10-digit random numbers.', 'antisocial-safety'); ?></label>
|
|
<?php
|
|
}
|
|
|
|
// Display admin notice if API key is not set
|
|
add_action('admin_notices', 'oai_admin_notices');
|
|
|
|
function oai_admin_notices() {
|
|
if (!current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
|
|
// Check if we're on the OpenAI Moderation settings page
|
|
$screen = get_current_screen();
|
|
if ($screen->id !== 'settings_page_openai-moderation') {
|
|
return;
|
|
}
|
|
|
|
$api_key = get_option('oai_api_key');
|
|
if (!$api_key) {
|
|
echo '<div class="notice notice-warning is-dismissible">
|
|
<p>' . __('OpenAI API key is not set. Moderation features are disabled until you enter a valid API key.', 'antisocial-safety') . '</p>
|
|
</div>';
|
|
}
|
|
}
|