/ wordpress / restai / includes / class-restai-comments.php
class-restai-comments.php
  1  <?php
  2  /**
  3   * AI-assisted comment moderation. Hooked into pre_comment_approved so the
  4   * decision is made before the comment is committed.
  5   *
  6   * @package RESTai
  7   */
  8  
  9  namespace RESTai;
 10  
 11  if ( ! defined( 'ABSPATH' ) ) {
 12  	exit;
 13  }
 14  
 15  class Comments {
 16  
 17  	/** @var Client */
 18  	private $client;
 19  
 20  	public function __construct( Client $client ) {
 21  		$this->client = $client;
 22  		add_filter( 'pre_comment_approved', array( $this, 'moderate' ), 9, 2 );
 23  	}
 24  
 25  	public function moderate( $approved, $commentdata ) {
 26  		$settings = get_option( 'restai_settings', array() );
 27  		if ( empty( $settings['enable_moderation'] ) ) {
 28  			return $approved;
 29  		}
 30  		// Already in spam/trash — don't waste a call.
 31  		if ( in_array( $approved, array( 'spam', 'trash' ), true ) ) {
 32  			return $approved;
 33  		}
 34  
 35  		$plugin     = Plugin::instance();
 36  		$project_id = $plugin->provisioner->project_for( 'comment_moderator' );
 37  		if ( $project_id <= 0 ) {
 38  			return $approved;
 39  		}
 40  
 41  		$author = isset( $commentdata['comment_author'] ) ? $commentdata['comment_author'] : '';
 42  		$body   = isset( $commentdata['comment_content'] ) ? $commentdata['comment_content'] : '';
 43  
 44  		$prompt = "AUTHOR: {$author}\nCOMMENT:\n{$body}";
 45  		$resp   = $this->client->ask( $project_id, $prompt );
 46  		if ( is_wp_error( $resp ) ) {
 47  			return $approved;
 48  		}
 49  
 50  		$json = $this->parse_json( $resp );
 51  		if ( ! is_array( $json ) ) {
 52  			return $approved;
 53  		}
 54  
 55  		if ( ! empty( $json['spam'] ) ) {
 56  			return 'spam';
 57  		}
 58  		if ( ! empty( $json['toxic'] ) ) {
 59  			return 0; // hold for moderation.
 60  		}
 61  
 62  		// Stash the analysis on the post for the moderator to inspect later.
 63  		add_action( 'comment_post', function ( $comment_id ) use ( $json ) {
 64  			update_comment_meta( $comment_id, '_restai_analysis', $json );
 65  		}, 10, 1 );
 66  
 67  		return $approved;
 68  	}
 69  
 70  	private function parse_json( $text ) {
 71  		$text = trim( (string) $text );
 72  		if ( '' === $text ) {
 73  			return null;
 74  		}
 75  		if ( preg_match( '/```(?:json)?\s*(\{.*?\})\s*```/s', $text, $m ) ) {
 76  			$d = json_decode( $m[1], true );
 77  			if ( is_array( $d ) ) {
 78  				return $d;
 79  			}
 80  		}
 81  		$stripped = preg_replace( '/^```(?:[a-z]+)?\s*/i', '', $text );
 82  		$stripped = preg_replace( '/\s*```$/', '', $stripped );
 83  		$d = json_decode( $stripped, true );
 84  		if ( is_array( $d ) ) {
 85  			return $d;
 86  		}
 87  		$start = strpos( $stripped, '{' );
 88  		while ( false !== $start ) {
 89  			$depth = 0;
 90  			$len   = strlen( $stripped );
 91  			for ( $i = $start; $i < $len; $i++ ) {
 92  				if ( $stripped[ $i ] === '{' ) {
 93  					$depth++;
 94  				} elseif ( $stripped[ $i ] === '}' ) {
 95  					$depth--;
 96  					if ( 0 === $depth ) {
 97  						$d = json_decode( substr( $stripped, $start, $i - $start + 1 ), true );
 98  						if ( is_array( $d ) ) {
 99  							return $d;
100  						}
101  						break;
102  					}
103  				}
104  			}
105  			$start = strpos( $stripped, '{', $start + 1 );
106  		}
107  		return null;
108  	}
109  }