Using ApyHub for Image Moderation
Tags: ai, laravel, php, apyhub
Published: 2024-05-31 09:05:25

In [Auctibles](https://auctibles.com), we use [ApyHub](https://apyhub.com) to moderate images of items for auction. Auctibles is built using the Laravel PHP framework. 

## Uploading Image Files

Videos are effortlessly uploaded using a straightforward Livewire component and an HTML `input` element with `type=file`.

	<input id="photos" name="photos" type="file" class="sr-only" wire:model.live="photos" accept="image/png,image/jpg,image/gif">

The component has a public property:

	public $photos = [];
	
Upon clicking a `submit` button, we apply validation to the field, 

    'photos' => 'nullable|array|max:3', // array
    'photos.*' => [
        'required',
        'image',
        'max:10240', // 10MB
        new ExplicitImage(),
    ],  

`ExplicitImage` is a validation rule.

## Temporary Files

We upload temporary files to a Minio bucket which resides on the server. The bucket is defined as an `uploads` bucket for Laravel. This is done in `config/filesystems.php`.

## ApyHub Results for Image Content

We use `curl` to call ApyHub. The response looks like this:

	{
	  "data": {
	    "apyhub": {
	      "adult": {
	        "adultScore": 0.0025164163671433926,
	        "goreScore": 0.0014069777680560946,
	        "isAdultContent": false,
	        "isGoryContent": false,
	        "isRacyContent": false,
	        "racyScore": 0.0032450903672724962
	      },
	      "metadata": {
	        "format": "Png",
	        "height": 1024,
	        "width": 1024
	      }
	    }
	  }
	}
	
## The Validation Rule
	
	<?php
	
	namespace App\Rules;
	
	use Closure;
	use Illuminate\Contracts\Validation\ValidationRule;
	use CURLFile;
	use CURLStringFile;
	use Illuminate\Support\Facades\Storage;
	
	class ExplicitImage implements ValidationRule
	{
	    /**
	     * Run the validation rule.
	     *
	     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
	     */
	    public function validate(string $attribute, mixed $value, Closure $fail): void
	    {
			    // path to temporary uploaded file in uploads disk
	        $path = config('livewire')['temporary_file_upload']['directory'] . DIRECTORY_SEPARATOR . $value->getFilename();
	
	        $ch = curl_init();
	
	        curl_setopt(
	          $ch,
	          CURLOPT_URL,
	          "https://api.apyhub.com/ai/image/detect/explicit-content/file"
	        );
	        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
	        curl_setopt($ch, CURLOPT_HTTPHEADER, [
	          'apy-token: ' . config('app.APYHUB_TOKEN'),
	          'content-type: multipart/form-data'
	        ]);
	        curl_setopt($ch, CURLOPT_POSTFIELDS, [
	            'file' => new CURLStringFile(Storage::disk('uploads')->get($path), $value->getFilename(), $value->getMimeType()),
	            'requested_service' => 'apyhub',
	        ]);
	
	        $response = curl_exec($ch);
	
	        curl_close($ch);
	
	        if ($response === false) {
	          $error = curl_error($ch);
	          logger()->warning("ApyHub image moderation CURL Error: $error");
	          return;
	        } else {
	          // Process the successful CURL call here.
	          $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	
	          if ($statusCode == 200) {
	            // The request was successful
	            $data = json_decode($response, true); //
	
	            $response = $data['data']['apyhub']['adult'];
	        
	            if ($response['isAdultContent'] || $response['isGoryContent'] || $response['isRacyContent']) {
	                $fail('validation.' . 'explicit_image')->translate(); 
	            }
	
	          } else {
	            // The server responded with an error
	            logger()->warning("ApyHub image moderation HTTP Error: $statusCode");
	            return;
	          }
	        }
	
	    }
	}