├── LICENSE ├── README.md ├── ajaximage.php ├── class.ImageFilter.php ├── db.php ├── index.php ├── loader.gif └── scripts ├── jquery.form.js ├── jquery.min.js └── jquery.wallform.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 David Garcia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP nudity and porn image detector 2 | 3 | PHP code by Bakr Alsharif from Egypt: 4 | http://www.9lessons.info/2014/01/block-uploads-of-adult-or-nude-images.html 5 | 6 | Live demo: 7 | http://demos.9lessons.info/ajaximageupload/index_m_block.php 8 | 9 | ## Block Uploads of Adult or Nude Images using PHP. 10 | 11 | I found an interesting and useful class file in phpclasses.org, that helps to detect image nudity based on skin pixel score developed by Bakr Alsharif from Egypt. I had integrated this with my previous tutorial Ajax image upload with Jquery and PHP, sure this code helps you to block adult or nudity images. 12 | 13 | ### Sample database design for Users. 14 | 15 | **Users** 16 | Contains user details username, password and email etc. 17 | ```sql 18 | CREATE TABLE `users` ( 19 | `uid` int(11) AUTO_INCREMENT PRIMARY KEY, 20 | `username` varchar(255) UNIQUE KEY, 21 | `password` varchar(100), 22 | `email` varchar(255) UNIQUE KEY 23 | ) 24 | ``` 25 | Sample values: 26 | ```sql 27 | INSERT INTO `users` 28 | (`uid`, `username`, `password`, `email`) 29 | VALUES 30 | ('1', '9lessons', MD5('password'), 'srinivas@9lessons.info'); 31 | ``` 32 | 33 | ### Javascript Code 34 | 35 | `$("#photoimg").on('change',function(){})` 36 | - photoimg is the ID name of INPUT FILE tag and 37 | 38 | `$('#imageform').ajaxForm()` 39 | - imageform is the ID name of FORM. 40 | 41 | While changing INPUT it calls FORM submit without refreshing page using `ajaxForm()` method. Uploaded images will `prepend` inside `#preview` tag. 42 | 43 | ```html 44 | 46 | 47 | 73 | ``` 74 | 75 | Here hiding and showing `#imageloadstatus` and `#imageloadbutton` based on form upload submit status. 76 | 77 | ### PHP Code 78 | 79 | **index.php** 80 | Contains simple PHP and HTML code. Here `$session_id=1` means user id session value. 81 | ```php 82 | 87 |
88 |
89 |
90 | Upload image: 91 | 92 |
93 | 94 | 95 |
96 |
97 | ``` 98 | 99 | **ajaximage.php** 100 | Contains PHP code. This script helps you to upload images into `uploads` folder. Image file name rename into `timestamp+session_id.extention` 101 | ```php 102 | GetScore($_FILES['photoimg']['tmp_name']); 136 | if(isset($score)) 137 | { 138 | if($score >= 60) // Score value If more than 60%, it consider as adult image. 139 | { 140 | echo "Image scored ".$score."%, It seems that you have uploaded a nude picture :-("; 141 | } 142 | else 143 | { 144 | //---Image Filter Code 145 | $actual_image_name = time().$session_id.".".$ext; 146 | $tmp = $_FILES['photoimg']['tmp_name']; 147 | if(move_uploaded_file($tmp, $path.$actual_image_name)) 148 | { 149 | mysqli_query($connection,"UPDATE users SET profile_image='$actual_image_name' WHERE uid='$session_id'"); 150 | echo ""; 151 | } 152 | else 153 | echo "failed"; 154 | //---Image Filter Code 155 | } 156 | } 157 | //---Image Filter Code 158 | } 159 | else 160 | echo "Image file size max 1 MB"; 161 | } 162 | else 163 | echo "Invalid file format.."; 164 | } 165 | else 166 | echo "Please select image..!"; 167 | exit; 168 | } 169 | ?> 170 | ``` 171 | 172 | **db.php** 173 | Database configuration file, just modify database credentials. 174 | ```php 175 | 183 | ``` 184 | 185 | -------------------------------------------------------------------------------- /ajaximage.php: -------------------------------------------------------------------------------- 1 | GetScore($_FILES['photoimg']['tmp_name']); 31 | 32 | if (isset($score)) { 33 | if ($score >= 40) { 34 | echo "Image scored ".$score."%, It seems that you have uploaded a nude picture :-("; 35 | } else { 36 | 37 | //--------- 38 | $actual_image_name = time().".".$ext; 39 | $tmp = $_FILES['photoimg']['tmp_name']; 40 | if (move_uploaded_file($tmp, $path.$actual_image_name)) { 41 | mysqli_query($connection, "UPDATE users SET profile_image='$actual_image_name' WHERE uid='$session_id'"); 42 | 43 | echo ""; 44 | } else { 45 | echo "Fail upload folder with read access."; 46 | } 47 | //-------- 48 | } 49 | } 50 | } else { 51 | echo "Image file size max 1 MB"; 52 | } 53 | } else { 54 | echo "Invalid file format.."; 55 | } 56 | } else { 57 | echo "Please select image..!"; 58 | } 59 | 60 | exit; 61 | } 62 | -------------------------------------------------------------------------------- /class.ImageFilter.php: -------------------------------------------------------------------------------- 1 | arA['R'] = ($this->colorA >> 16) & 0xFF; 39 | $this->arA['G'] = ($this->colorA >> 8) & 0xFF; 40 | $this->arA['B'] = $this->colorA & 0xFF; 41 | 42 | $this->arB['R'] = ($this->colorB >> 16) & 0xFF; 43 | $this->arB['G'] = ($this->colorB >> 8) & 0xFF; 44 | $this->arB['B'] = $this->colorB & 0xFF; 45 | } 46 | 47 | public function GetScore($image) 48 | { 49 | $x = 0; 50 | $y = 0; 51 | $img = $this->_GetImageResource($image, $x, $y); 52 | if (!$img) { 53 | return false; 54 | } 55 | 56 | $score = 0; 57 | 58 | $xPoints = array($x/8, $x/4, ($x/8 + $x/4), $x-($x/8 + $x/4), $x-($x/4), $x-($x/8)); 59 | $yPoints = array($y/8, $y/4, ($y/8 + $y/4), $y-($y/8 + $y/4), $y-($y/8), $y-($y/8)); 60 | $zPoints = array($xPoints[2], $yPoints[1], $xPoints[3], $y); 61 | 62 | 63 | for ($i=1; $i<=$x; $i++) { 64 | for ($j=1; $j<=$y; $j++) { 65 | $color = imagecolorat($img, $i, $j); 66 | if ($color >= $this->colorA && $color <= $this->colorB) { 67 | $color = array('R'=> ($color >> 16) & 0xFF, 'G'=> ($color >> 8) & 0xFF, 'B'=> $color & 0xFF); 68 | if ($color['G'] >= $this->arA['G'] && $color['G'] <= $this->arB['G'] && $color['B'] >= $this->arA['B'] && $color['B'] <= $this->arB['B']) { 69 | if ($i >= $zPoints[0] && $j >= $zPoints[1] && $i <= $zPoints[2] && $j <= $zPoints[3]) { 70 | $score += 3; 71 | } elseif ($i <= $xPoints[0] || $i >=$xPoints[5] || $j <= $yPoints[0] || $j >= $yPoints[5]) { 72 | $score += 0.10; 73 | } elseif ($i <= $xPoints[0] || $i >=$xPoints[4] || $j <= $yPoints[0] || $j >= $yPoints[4]) { 74 | $score += 0.40; 75 | } else { 76 | $score += 1.50; 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | imagedestroy($img); 84 | 85 | $score = sprintf('%01.2f', ($score * 100) / ($x * $y)); 86 | if ($score > 100) { 87 | $score = 100; 88 | } 89 | return $score; 90 | } 91 | 92 | public function GetScoreAndFill($image, $outputImage) 93 | { 94 | $x = 0; 95 | $y = 0; 96 | $img = $this->_GetImageResource($image, $x, $y); 97 | if (!$img) { 98 | return false; 99 | } 100 | 101 | $score = 0; 102 | 103 | $xPoints = array($x/8, $x/4, ($x/8 + $x/4), $x-($x/8 + $x/4), $x-($x/4), $x-($x/8)); 104 | $yPoints = array($y/8, $y/4, ($y/8 + $y/4), $y-($y/8 + $y/4), $y-($y/8), $y-($y/8)); 105 | $zPoints = array($xPoints[2], $yPoints[1], $xPoints[3], $y); 106 | 107 | 108 | for ($i=1; $i<=$x; $i++) { 109 | for ($j=1; $j<=$y; $j++) { 110 | $color = imagecolorat($img, $i, $j); 111 | if ($color >= $this->colorA && $color <= $this->colorB) { 112 | $color = array('R'=> ($color >> 16) & 0xFF, 'G'=> ($color >> 8) & 0xFF, 'B'=> $color & 0xFF); 113 | if ($color['G'] >= $this->arA['G'] && $color['G'] <= $this->arB['G'] && $color['B'] >= $this->arA['B'] && $color['B'] <= $this->arB['B']) { 114 | if ($i >= $zPoints[0] && $j >= $zPoints[1] && $i <= $zPoints[2] && $j <= $zPoints[3]) { 115 | $score += 3; 116 | imagefill($img, $i, $j, 16711680); 117 | } elseif ($i <= $xPoints[0] || $i >=$xPoints[5] || $j <= $yPoints[0] || $j >= $yPoints[5]) { 118 | $score += 0.10; 119 | imagefill($img, $i, $j, 14540253); 120 | } elseif ($i <= $xPoints[0] || $i >=$xPoints[4] || $j <= $yPoints[0] || $j >= $yPoints[4]) { 121 | $score += 0.40; 122 | imagefill($img, $i, $j, 16514887); 123 | } else { 124 | $score += 1.50; 125 | imagefill($img, $i, $j, 512); 126 | } 127 | } 128 | } 129 | } 130 | } 131 | imagejpeg($img, $outputImage); 132 | 133 | imagedestroy($img); 134 | 135 | $score = sprintf('%01.2f', ($score * 100) / ($x * $y)); 136 | if ($score > 100) { 137 | $score = 100; 138 | } 139 | return $score; 140 | } 141 | 142 | public function _GetImageResource($image, &$x, &$y) 143 | { 144 | $info = GetImageSize($image); 145 | 146 | $x = $info[0]; 147 | $y = $info[1]; 148 | 149 | switch ($info[2]) { 150 | case IMAGETYPE_GIF: 151 | return @ImageCreateFromGif($image); 152 | 153 | case IMAGETYPE_JPEG: 154 | return @ImageCreateFromJpeg($image); 155 | 156 | case IMAGETYPE_PNG: 157 | return @ImageCreateFromPng($image); 158 | 159 | default: 160 | return false; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /db.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | Ajax Image Upload Block Adult Images 9lessons blog 9 | 10 | 11 | 12 | 13 | 14 | 42 | 43 | 62 | 63 | 9lessons.info 64 | 65 | 66 |
67 | 68 |
69 |
70 | 71 | 72 | 73 |
74 | Upload your image 75 | 76 |
77 | 78 |
79 |
80 | 81 | 82 | 83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidGarciaCat/php-nudity-image-detector/0e860bd77e7755ecf0b3d3b11d7bade6b4f5af64/loader.gif -------------------------------------------------------------------------------- /scripts/jquery.form.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Form Plugin 3 | * version: 2.84 (12-AUG-2011) 4 | * @requires jQuery v1.3.2 or later 5 | * 6 | * Examples and documentation at: http://malsup.com/jquery/form/ 7 | * Dual licensed under the MIT and GPL licenses: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * http://www.gnu.org/licenses/gpl.html 10 | */ 11 | ;(function($) { 12 | 13 | /* 14 | Usage Note: 15 | ----------- 16 | Do not use both ajaxSubmit and ajaxForm on the same form. These 17 | functions are intended to be exclusive. Use ajaxSubmit if you want 18 | to bind your own submit handler to the form. For example, 19 | 20 | $(document).ready(function() { 21 | $('#myForm').bind('submit', function(e) { 22 | e.preventDefault(); // <-- important 23 | $(this).ajaxSubmit({ 24 | target: '#output' 25 | }); 26 | }); 27 | }); 28 | 29 | Use ajaxForm when you want the plugin to manage all the event binding 30 | for you. For example, 31 | 32 | $(document).ready(function() { 33 | $('#myForm').ajaxForm({ 34 | target: '#output' 35 | }); 36 | }); 37 | 38 | When using ajaxForm, the ajaxSubmit function will be invoked for you 39 | at the appropriate time. 40 | */ 41 | 42 | /** 43 | * ajaxSubmit() provides a mechanism for immediately submitting 44 | * an HTML form using AJAX. 45 | */ 46 | $.fn.ajaxSubmit = function(options) { 47 | // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) 48 | if (!this.length) { 49 | log('ajaxSubmit: skipping submit process - no element selected'); 50 | return this; 51 | } 52 | 53 | var method, action, url, $form = this; 54 | 55 | if (typeof options == 'function') { 56 | options = { success: options }; 57 | } 58 | 59 | method = this.attr('method'); 60 | action = this.attr('action'); 61 | url = (typeof action === 'string') ? $.trim(action) : ''; 62 | url = url || window.location.href || ''; 63 | if (url) { 64 | // clean url (don't include hash vaue) 65 | url = (url.match(/^([^#]+)/)||[])[1]; 66 | } 67 | 68 | options = $.extend(true, { 69 | url: url, 70 | success: $.ajaxSettings.success, 71 | type: method || 'GET', 72 | iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' 73 | }, options); 74 | 75 | // hook for manipulating the form data before it is extracted; 76 | // convenient for use with rich editors like tinyMCE or FCKEditor 77 | var veto = {}; 78 | this.trigger('form-pre-serialize', [this, options, veto]); 79 | if (veto.veto) { 80 | log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); 81 | return this; 82 | } 83 | 84 | // provide opportunity to alter form data before it is serialized 85 | if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { 86 | log('ajaxSubmit: submit aborted via beforeSerialize callback'); 87 | return this; 88 | } 89 | 90 | var n,v,a = this.formToArray(options.semantic); 91 | if (options.data) { 92 | options.extraData = options.data; 93 | for (n in options.data) { 94 | if( $.isArray(options.data[n]) ) { 95 | for (var k in options.data[n]) { 96 | a.push( { name: n, value: options.data[n][k] } ); 97 | } 98 | } 99 | else { 100 | v = options.data[n]; 101 | v = $.isFunction(v) ? v() : v; // if value is fn, invoke it 102 | a.push( { name: n, value: v } ); 103 | } 104 | } 105 | } 106 | 107 | // give pre-submit callback an opportunity to abort the submit 108 | if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { 109 | log('ajaxSubmit: submit aborted via beforeSubmit callback'); 110 | return this; 111 | } 112 | 113 | // fire vetoable 'validate' event 114 | this.trigger('form-submit-validate', [a, this, options, veto]); 115 | if (veto.veto) { 116 | log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); 117 | return this; 118 | } 119 | 120 | var q = $.param(a); 121 | 122 | if (options.type.toUpperCase() == 'GET') { 123 | options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; 124 | options.data = null; // data is null for 'get' 125 | } 126 | else { 127 | options.data = q; // data is the query string for 'post' 128 | } 129 | 130 | var callbacks = []; 131 | if (options.resetForm) { 132 | callbacks.push(function() { $form.resetForm(); }); 133 | } 134 | if (options.clearForm) { 135 | callbacks.push(function() { $form.clearForm(); }); 136 | } 137 | 138 | // perform a load on the target only if dataType is not provided 139 | if (!options.dataType && options.target) { 140 | var oldSuccess = options.success || function(){}; 141 | callbacks.push(function(data) { 142 | var fn = options.replaceTarget ? 'replaceWith' : 'html'; 143 | $(options.target)[fn](data).each(oldSuccess, arguments); 144 | }); 145 | } 146 | else if (options.success) { 147 | callbacks.push(options.success); 148 | } 149 | 150 | options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg 151 | var context = options.context || options; // jQuery 1.4+ supports scope context 152 | for (var i=0, max=callbacks.length; i < max; i++) { 153 | callbacks[i].apply(context, [data, status, xhr || $form, $form]); 154 | } 155 | }; 156 | 157 | // are there files to upload? 158 | var fileInputs = $('input:file', this).length > 0; 159 | var mp = 'multipart/form-data'; 160 | var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); 161 | 162 | // options.iframe allows user to force iframe mode 163 | // 06-NOV-09: now defaulting to iframe mode if file input is detected 164 | if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { 165 | // hack to fix Safari hang (thanks to Tim Molendijk for this) 166 | // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d 167 | if (options.closeKeepAlive) { 168 | $.get(options.closeKeepAlive, function() { fileUpload(a); }); 169 | } 170 | else { 171 | fileUpload(a); 172 | } 173 | } 174 | else { 175 | // IE7 massage (see issue 57) 176 | if ($.browser.msie && method == 'get') { 177 | var ieMeth = $form[0].getAttribute('method'); 178 | if (typeof ieMeth === 'string') 179 | options.type = ieMeth; 180 | } 181 | $.ajax(options); 182 | } 183 | 184 | // fire 'notify' event 185 | this.trigger('form-submit-notify', [this, options]); 186 | return this; 187 | 188 | 189 | // private function for handling file uploads (hat tip to YAHOO!) 190 | function fileUpload(a) { 191 | var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; 192 | var useProp = !!$.fn.prop; 193 | 194 | if (a) { 195 | // ensure that every serialized input is still enabled 196 | for (i=0; i < a.length; i++) { 197 | el = $(form[a[i].name]); 198 | el[ useProp ? 'prop' : 'attr' ]('disabled', false); 199 | } 200 | } 201 | 202 | if ($(':input[name=submit],:input[id=submit]', form).length) { 203 | // if there is an input with a name or id of 'submit' then we won't be 204 | // able to invoke the submit fn on the form (at least not x-browser) 205 | alert('Error: Form elements must not have name or id of "submit".'); 206 | return; 207 | } 208 | 209 | s = $.extend(true, {}, $.ajaxSettings, options); 210 | s.context = s.context || s; 211 | id = 'jqFormIO' + (new Date().getTime()); 212 | if (s.iframeTarget) { 213 | $io = $(s.iframeTarget); 214 | n = $io.attr('name'); 215 | if (n == null) 216 | $io.attr('name', id); 217 | else 218 | id = n; 219 | } 220 | else { 221 | $io = $('