Source: Providers/OpenAI/Moderation.php

  1. <?php
  2. /**
  3. * OpenAI Moderation integration
  4. */
  5. namespace Classifai\Providers\OpenAI;
  6. use Classifai\Providers\Provider;
  7. use Classifai\Features\Moderation as ModerationFeature;
  8. use WP_Error;
  9. class Moderation extends Provider {
  10. use OpenAI;
  11. const ID = 'openai_moderation';
  12. /**
  13. * OpenAI Moderation URL
  14. *
  15. * @var string
  16. */
  17. protected $moderation_url = 'https://api.openai.com/v1/moderations';
  18. /**
  19. * OpenAI Moderation model
  20. *
  21. * @var string
  22. */
  23. protected $model = 'omni-moderation-latest';
  24. /**
  25. * OpenAI Moderation constructor.
  26. *
  27. * @param \Classifai\Features\Feature $feature_instance The feature instance.
  28. */
  29. public function __construct( $feature_instance = null ) {
  30. $this->feature_instance = $feature_instance;
  31. }
  32. /**
  33. * Get the model name.
  34. *
  35. * @return string
  36. */
  37. public function get_model(): string {
  38. /**
  39. * Filter the model name.
  40. *
  41. * Useful if you want to use a different model, like
  42. * text-moderation-latest.
  43. *
  44. * @since x.x.x
  45. * @hook classifai_openai_moderation_model
  46. *
  47. * @param {string} $model The default model to use.
  48. *
  49. * @return {string} The model to use.
  50. */
  51. return apply_filters( 'classifai_openai_moderation_model', $this->model );
  52. }
  53. /**
  54. * Register what we need for the provider.
  55. */
  56. public function register() {
  57. }
  58. /**
  59. * Render the provider fields.
  60. */
  61. public function render_provider_fields() {
  62. $settings = $this->feature_instance->get_settings( static::ID );
  63. add_settings_field(
  64. static::ID . '_api_key',
  65. esc_html__( 'API Key', 'classifai' ),
  66. [ $this->feature_instance, 'render_input' ],
  67. $this->feature_instance->get_option_name(),
  68. $this->feature_instance->get_option_name() . '_section',
  69. [
  70. 'option_index' => static::ID,
  71. 'label_for' => 'api_key',
  72. 'input_type' => 'password',
  73. 'default_value' => $settings['api_key'],
  74. 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this.
  75. 'description' => $this->feature_instance->is_configured_with_provider( static::ID ) ?
  76. '' :
  77. sprintf(
  78. wp_kses(
  79. /* translators: %1$s is replaced with the OpenAI sign up URL */
  80. __( 'Don\'t have an OpenAI account yet? <a title="Sign up for an OpenAI account" href="%1$s">Sign up for one</a> in order to get your API key.', 'classifai' ),
  81. [
  82. 'a' => [
  83. 'href' => [],
  84. 'title' => [],
  85. ],
  86. ]
  87. ),
  88. esc_url( 'https://platform.openai.com/signup' )
  89. ),
  90. ]
  91. );
  92. do_action( 'classifai_' . static::ID . '_render_provider_fields', $this );
  93. }
  94. /**
  95. * Returns the default settings for this provider.
  96. *
  97. * @return array
  98. */
  99. public function get_default_provider_settings(): array {
  100. $common_settings = [
  101. 'api_key' => '',
  102. 'authenticated' => false,
  103. ];
  104. return $common_settings;
  105. }
  106. /**
  107. * Sanitize the settings for this provider.
  108. *
  109. * @param array $new_settings The settings array.
  110. * @return array
  111. */
  112. public function sanitize_settings( array $new_settings ): array {
  113. $settings = $this->feature_instance->get_settings();
  114. $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings );
  115. $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key'];
  116. $new_settings[ static::ID ]['authenticated'] = $api_key_settings[ static::ID ]['authenticated'];
  117. return $new_settings;
  118. }
  119. /**
  120. * Sanitize the API key.
  121. *
  122. * @param array $new_settings The settings array.
  123. * @return string
  124. */
  125. public function sanitize_api_key( array $new_settings ): string {
  126. $settings = $this->feature_instance->get_settings();
  127. return sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ?? '' );
  128. }
  129. /**
  130. * Common entry point for all REST endpoints for this provider.
  131. *
  132. * @param int $item_id The item ID we're processing.
  133. * @param string $route_to_call The route we are processing.
  134. * @param array $args Optional arguments to pass to the route.
  135. * @return array|WP_Error
  136. */
  137. public function rest_endpoint_callback( $item_id = 0, string $route_to_call = '', array $args = [] ) {
  138. $route_to_call = strtolower( $route_to_call );
  139. $return = [];
  140. // Handle all of our routes.
  141. switch ( $route_to_call ) {
  142. case 'comment':
  143. $return = $this->moderate_comment( $item_id );
  144. break;
  145. case 'post':
  146. $return = [];
  147. break;
  148. }
  149. return $return;
  150. }
  151. /**
  152. * Send comment to remote service for moderation.
  153. *
  154. * @param int $comment_id Attachment ID to process.
  155. * @return array|WP_Error
  156. */
  157. public function moderate_comment( int $comment_id = 0 ) {
  158. // Ensure we have a valid comment.
  159. if ( ! $comment_id || ! get_comment( $comment_id ) ) {
  160. return new WP_Error( 'valid_id_required', esc_html__( 'A valid comment ID is required to run moderation.', 'classifai' ) );
  161. }
  162. // Ensure the current user has proper permissions.
  163. if (
  164. ! current_user_can( 'moderate_comments' ) ||
  165. ! current_user_can( 'edit_comment', $comment_id )
  166. ) {
  167. return new WP_Error( 'permission_denied', esc_html__( 'You do not have permission to moderate comments.', 'classifai' ) );
  168. }
  169. $feature = new ModerationFeature();
  170. $settings = $feature->get_settings();
  171. // Ensure the feature is enabled and the user has access.
  172. if (
  173. ! $feature->is_feature_enabled() ||
  174. ! in_array( 'comments', $feature->get_moderation_content_settings(), true )
  175. ) {
  176. return new WP_Error( 'not_enabled', esc_html__( 'Moderation is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) );
  177. }
  178. $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() );
  179. $comment = get_comment( $comment_id );
  180. /**
  181. * Filter the request body before sending to OpenAI.
  182. *
  183. * @since 3.0.0
  184. * @hook classifai_openai_moderation_request_body
  185. *
  186. * @param {array} $body Request body that will be sent to OpenAI.
  187. * @param {int} $comment_id ID of comment we are moderating.
  188. *
  189. * @return {array} Request body.
  190. */
  191. $body = apply_filters(
  192. 'classifai_openai_moderation_request_body',
  193. [
  194. 'input' => $comment->comment_content,
  195. 'model' => $this->get_model(),
  196. ],
  197. $comment_id
  198. );
  199. // Make our API request.
  200. $response = $request->post(
  201. $this->moderation_url,
  202. [
  203. 'body' => wp_json_encode( $body ),
  204. ]
  205. );
  206. set_transient( 'classifai_openai_moderation_latest_response', $response, DAY_IN_SECONDS * 30 );
  207. if ( is_wp_error( $response ) ) {
  208. return $response;
  209. }
  210. return $response['results'][0];
  211. }
  212. /**
  213. * Returns the debug information for the provider settings.
  214. *
  215. * @return array
  216. */
  217. public function get_debug_information() {
  218. $settings = $this->feature_instance->get_settings();
  219. $provider_settings = $settings[ static::ID ];
  220. $debug_info = [];
  221. if ( $this->feature_instance instanceof ModerationFeature ) {
  222. $debug_info[ __( 'Content to Moderate', 'classifai' ) ] = implode( ', ', $provider_settings['content_types'] ?? [] );
  223. $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_moderation_latest_response' ) );
  224. }
  225. return apply_filters(
  226. 'classifai_' . self::ID . '_debug_information',
  227. $debug_info,
  228. $settings,
  229. $this->feature_instance
  230. );
  231. }
  232. }