ILIAS  Release_4_4_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
securimage.php
Go to the documentation of this file.
1 <?php
2 
3 // error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
4 
166 {
167  // All of the public variables below are securimage options
168  // They can be passed as an array to the Securimage constructor, set below,
169  // or set from securimage_show.php and securimage_play.php
170 
175  const SI_IMAGE_JPEG = 1;
180  const SI_IMAGE_PNG = 2;
185  const SI_IMAGE_GIF = 3;
186 
191  const SI_CAPTCHA_STRING = 0;
201  const SI_CAPTCHA_WORDS = 2;
202 
208  const SI_DRIVER_MYSQL = 'mysql';
209 
215  const SI_DRIVER_PGSQL = 'pgsql';
216 
222  const SI_DRIVER_SQLITE3 = 'sqlite';
223 
224  /*%*********************************************************************%*/
225  // Properties
226 
231  public $image_width = 215;
236  public $image_height = 80;
242 
247  public $image_bg_color = '#ffffff';
252  public $text_color = '#707070';
257  public $line_color = '#707070';
262  public $noise_color = '#707070';
263 
273  public $use_transparent_text = true;
274 
279  public $code_length = 6;
284  public $case_sensitive = false;
289  public $charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
294  public $expiry_time = 900;
295 
301  public $session_name = null;
302 
307  public $use_wordlist = false;
308 
313  public $perturbation = 0.85;
318  public $num_lines = 5;
323  public $noise_level = 2;
324 
329  public $image_signature = '';
334  public $signature_color = '#707070';
340 
348  public $use_sqlite_db = false;
349 
358  public $use_database = false;
359 
368 
376  public $database_host = 'localhost';
377 
384  public $database_user = '';
385 
392  public $database_pass = '';
393 
400  public $database_name = '';
401 
409  public $database_table = 'captcha_codes';
410 
419 
425  public $captcha_type = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC;
426 
442  public $namespace;
443 
448  public $ttf_file;
472  public $audio_path;
526  public $audio_gap_min = 0;
533  public $audio_gap_max = 600;
534 
539  protected static $_captchaId = null;
540 
541  protected $im;
542  protected $tmpimg;
543  protected $bgimg;
544  protected $iscale = 5;
545 
546  public $securimage_path = null;
547 
553  protected $code;
554 
560  protected $code_display;
561 
571 
577  protected $captcha_code;
578 
584  protected $no_exit;
585 
591  protected $no_session;
592 
598  protected $send_headers;
599 
605  protected $pdo_conn;
606 
607  // gd color resources that are allocated for drawing the image
608  protected $gdbgcolor;
609  protected $gdtextcolor;
610  protected $gdlinecolor;
611  protected $gdsignaturecolor;
612 
629  public function __construct($options = array())
630  {
631  $this->securimage_path = dirname(__FILE__);
632 
633  if (is_array($options) && sizeof($options) > 0) {
634  foreach($options as $prop => $val) {
635  if ($prop == 'captchaId') {
637  $this->use_database = true;
638  } else if ($prop == 'use_sqlite_db') {
639  trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE);
640  } else {
641  $this->$prop = $val;
642  }
643  }
644  }
645 
646  $this->image_bg_color = $this->initColor($this->image_bg_color, '#ffffff');
647  $this->text_color = $this->initColor($this->text_color, '#616161');
648  $this->line_color = $this->initColor($this->line_color, '#616161');
649  $this->noise_color = $this->initColor($this->noise_color, '#616161');
650  $this->signature_color = $this->initColor($this->signature_color, '#616161');
651 
652  if (is_null($this->ttf_file)) {
653  $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
654  }
655 
656  $this->signature_font = $this->ttf_file;
657 
658  if (is_null($this->wordlist_file)) {
659  $this->wordlist_file = $this->securimage_path . '/words/words.txt';
660  }
661 
662  if (is_null($this->database_file)) {
663  $this->database_file = $this->securimage_path . '/database/securimage.sq3';
664  }
665 
666  if (is_null($this->audio_path)) {
667  $this->audio_path = $this->securimage_path . '/audio/en/';
668  }
669 
670  if (is_null($this->audio_noise_path)) {
671  $this->audio_noise_path = $this->securimage_path . '/audio/noise/';
672  }
673 
674  if (is_null($this->audio_use_noise)) {
675  $this->audio_use_noise = true;
676  }
677 
678  if (is_null($this->degrade_audio)) {
679  $this->degrade_audio = true;
680  }
681 
682  if (is_null($this->code_length) || (int)$this->code_length < 1) {
683  $this->code_length = 6;
684  }
685 
686  if (is_null($this->perturbation) || !is_numeric($this->perturbation)) {
687  $this->perturbation = 0.75;
688  }
689 
690  if (is_null($this->namespace) || !is_string($this->namespace)) {
691  $this->namespace = 'default';
692  }
693 
694  if (is_null($this->no_exit)) {
695  $this->no_exit = false;
696  }
697 
698  if (is_null($this->no_session)) {
699  $this->no_session = false;
700  }
701 
702  if (is_null($this->send_headers)) {
703  $this->send_headers = true;
704  }
705 
706  if ($this->no_session != true) {
707  // Initialize session or attach to existing
708  if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
709  if (!is_null($this->session_name) && trim($this->session_name) != '') {
710  session_name(trim($this->session_name)); // set session name if provided
711  }
712  session_start();
713  }
714  }
715  }
716 
721  public static function getPath()
722  {
723  return dirname(__FILE__);
724  }
725 
735  public static function getCaptchaId($new = true, array $options = array())
736  {
737  if (is_null($new) || (bool)$new == true) {
738  $id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true));
739  $opts = array('no_session' => true,
740  'use_database' => true);
741  if (sizeof($options) > 0) $opts = array_merge($options, $opts);
742  $si = new self($opts);
744  $si->createCode();
745 
746  return $id;
747  } else {
749  }
750  }
751 
763  public static function checkByCaptchaId($id, $value, array $options = array())
764  {
765  $opts = array('captchaId' => $id,
766  'no_session' => true,
767  'use_database' => true);
768 
769  if (sizeof($options) > 0) $opts = array_merge($options, $opts);
770 
771  $si = new self($opts);
772 
773  if ($si->openDatabase()) {
774  $code = $si->getCodeFromDatabase();
775 
776  if (is_array($code)) {
777  $si->code = $code['code'];
778  $si->code_display = $code['code_disp'];
779  }
780 
781  if ($si->check($value)) {
782  $si->clearCodeFromDatabase();
783 
784  return true;
785  } else {
786  return false;
787  }
788  } else {
789  return false;
790  }
791  }
792 
793 
807  public function show($background_image = '')
808  {
809  set_error_handler(array(&$this, 'errorHandler'));
810 
811  if($background_image != '' && is_readable($background_image)) {
812  $this->bgimg = $background_image;
813  }
814 
815  $this->doImage();
816  }
817 
831  public function check($code)
832  {
833  $this->code_entered = $code;
834  $this->validate();
835  return $this->correct_code;
836  }
837 
847  public function outputAudioFile()
848  {
849  set_error_handler(array(&$this, 'errorHandler'));
850 
851  require_once dirname(__FILE__) . '/WavFile.php';
852 
853  try {
854  $audio = $this->getAudibleCode();
855  } catch (Exception $ex) {
856  if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false) {
857  fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n");
858  fclose($fp);
859  }
860 
861  $audio = $this->audioError();
862  }
863 
864  if ($this->canSendHeaders() || $this->send_headers == false) {
865  if ($this->send_headers) {
866  $uniq = md5(uniqid(microtime()));
867  header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.wav\"");
868  header('Cache-Control: no-store, no-cache, must-revalidate');
869  header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
870  header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
871  header('Content-type: audio/x-wav');
872 
873  if (extension_loaded('zlib')) {
874  ini_set('zlib.output_compression', true); // compress output if supported by browser
875  } else {
876  header('Content-Length: ' . strlen($audio));
877  }
878  }
879 
880  echo $audio;
881  } else {
882  echo '<hr /><strong>'
883  .'Failed to generate audio file, content has already been '
884  .'output.<br />This is most likely due to misconfiguration or '
885  .'a PHP error was sent to the browser.</strong>';
886  }
887 
888  restore_error_handler();
889 
890  if (!$this->no_exit) exit;
891  }
892 
899  public function getCode($array = false, $returnExisting = false)
900  {
901  $code = '';
902  $time = 0;
903  $disp = 'error';
904 
905  if ($returnExisting && strlen($this->code) > 0) {
906  if ($array) {
907  return array('code' => $this->code,
908  'display' => $this->code_display,
909  'code_display' => $this->code_display,
910  'time' => 0);
911  } else {
912  return $this->code;
913  }
914  }
915 
916  if ($this->no_session != true) {
917  if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
918  trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
919  if ($this->isCodeExpired(
920  $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
921  $code = $_SESSION['securimage_code_value'][$this->namespace];
922  $time = $_SESSION['securimage_code_ctime'][$this->namespace];
923  $disp = $_SESSION['securimage_code_disp'] [$this->namespace];
924  }
925  }
926  }
927 
928  if (empty($code) && $this->use_database) {
929  // no code in session - may mean user has cookies turned off
930  $this->openDatabase();
931  $code = $this->getCodeFromDatabase();
932  } else { /* no code stored in session or sqlite database, validation will fail */ }
933 
934  if ($array == true) {
935  return array('code' => $code, 'ctime' => $time, 'display' => $disp);
936  } else {
937  return $code;
938  }
939  }
940 
944  protected function doImage()
945  {
946  if( ($this->use_transparent_text == true || $this->bgimg != '') && function_exists('imagecreatetruecolor')) {
947  $imagecreate = 'imagecreatetruecolor';
948  } else {
949  $imagecreate = 'imagecreate';
950  }
951 
952  $this->im = $imagecreate($this->image_width, $this->image_height);
953  $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
954 
955  $this->allocateColors();
956  imagepalettecopy($this->tmpimg, $this->im);
957 
958  $this->setBackground();
959 
960  $code = '';
961 
962  if ($this->getCaptchaId(false) !== null) {
963  // a captcha Id was supplied
964 
965  // check to see if a display_value for the captcha image was set
966  if (is_string($this->display_value) && strlen($this->display_value) > 0) {
967  $this->code_display = $this->display_value;
968  $this->code = ($this->case_sensitive) ?
969  $this->display_value :
970  strtolower($this->display_value);
971  $code = $this->code;
972  } else if ($this->openDatabase()) {
973  // no display_value, check the database for existing captchaId
974  $code = $this->getCodeFromDatabase();
975 
976  // got back a result from the database with a valid code for captchaId
977  if (is_array($code)) {
978  $this->code = $code['code'];
979  $this->code_display = $code['code_disp'];
980  $code = $code['code'];
981  }
982  }
983  }
984 
985  if ($code == '') {
986  // if the code was not set using display_value or was not found in
987  // the database, create a new code
988  $this->createCode();
989  }
990 
991  if ($this->noise_level > 0) {
992  $this->drawNoise();
993  }
994 
995  $this->drawWord();
996 
997  if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
998  $this->distortedCopy();
999  }
1000 
1001  if ($this->num_lines > 0) {
1002  $this->drawLines();
1003  }
1004 
1005  if (trim($this->image_signature) != '') {
1006  $this->addSignature();
1007  }
1008 
1009  $this->output();
1010  }
1011 
1015  protected function allocateColors()
1016  {
1017  // allocate bg color first for imagecreate
1018  $this->gdbgcolor = imagecolorallocate($this->im,
1019  $this->image_bg_color->r,
1020  $this->image_bg_color->g,
1021  $this->image_bg_color->b);
1022 
1023  $alpha = intval($this->text_transparency_percentage / 100 * 127);
1024 
1025  if ($this->use_transparent_text == true) {
1026  $this->gdtextcolor = imagecolorallocatealpha($this->im,
1027  $this->text_color->r,
1028  $this->text_color->g,
1029  $this->text_color->b,
1030  $alpha);
1031  $this->gdlinecolor = imagecolorallocatealpha($this->im,
1032  $this->line_color->r,
1033  $this->line_color->g,
1034  $this->line_color->b,
1035  $alpha);
1036  $this->gdnoisecolor = imagecolorallocatealpha($this->im,
1037  $this->noise_color->r,
1038  $this->noise_color->g,
1039  $this->noise_color->b,
1040  $alpha);
1041  } else {
1042  $this->gdtextcolor = imagecolorallocate($this->im,
1043  $this->text_color->r,
1044  $this->text_color->g,
1045  $this->text_color->b);
1046  $this->gdlinecolor = imagecolorallocate($this->im,
1047  $this->line_color->r,
1048  $this->line_color->g,
1049  $this->line_color->b);
1050  $this->gdnoisecolor = imagecolorallocate($this->im,
1051  $this->noise_color->r,
1052  $this->noise_color->g,
1053  $this->noise_color->b);
1054  }
1055 
1056  $this->gdsignaturecolor = imagecolorallocate($this->im,
1057  $this->signature_color->r,
1058  $this->signature_color->g,
1059  $this->signature_color->b);
1060 
1061  }
1062 
1066  protected function setBackground()
1067  {
1068  // set background color of image by drawing a rectangle since imagecreatetruecolor doesn't set a bg color
1069  imagefilledrectangle($this->im, 0, 0,
1070  $this->image_width, $this->image_height,
1071  $this->gdbgcolor);
1072  imagefilledrectangle($this->tmpimg, 0, 0,
1073  $this->image_width * $this->iscale, $this->image_height * $this->iscale,
1074  $this->gdbgcolor);
1075 
1076  if ($this->bgimg == '') {
1077  if ($this->background_directory != null &&
1078  is_dir($this->background_directory) &&
1079  is_readable($this->background_directory))
1080  {
1081  $img = $this->getBackgroundFromDirectory();
1082  if ($img != false) {
1083  $this->bgimg = $img;
1084  }
1085  }
1086  }
1087 
1088  if ($this->bgimg == '') {
1089  return;
1090  }
1091 
1092  $dat = @getimagesize($this->bgimg);
1093  if($dat == false) {
1094  return;
1095  }
1096 
1097  switch($dat[2]) {
1098  case 1: $newim = @imagecreatefromgif($this->bgimg); break;
1099  case 2: $newim = @imagecreatefromjpeg($this->bgimg); break;
1100  case 3: $newim = @imagecreatefrompng($this->bgimg); break;
1101  default: return;
1102  }
1103 
1104  if(!$newim) return;
1105 
1106  imagecopyresized($this->im, $newim, 0, 0, 0, 0,
1107  $this->image_width, $this->image_height,
1108  imagesx($newim), imagesy($newim));
1109  }
1110 
1114  protected function getBackgroundFromDirectory()
1115  {
1116  $images = array();
1117 
1118  if ( ($dh = opendir($this->background_directory)) !== false) {
1119  while (($file = readdir($dh)) !== false) {
1120  if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file;
1121  }
1122 
1123  closedir($dh);
1124 
1125  if (sizeof($images) > 0) {
1126  return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)];
1127  }
1128  }
1129 
1130  return false;
1131  }
1132 
1136  public function createCode()
1137  {
1138  $this->code = false;
1139 
1140  switch($this->captcha_type) {
1141  case self::SI_CAPTCHA_MATHEMATIC:
1142  {
1143  do {
1144  $signs = array('+', '-', 'x');
1145  $left = mt_rand(1, 10);
1146  $right = mt_rand(1, 5);
1147  $sign = $signs[mt_rand(0, 2)];
1148 
1149  switch($sign) {
1150  case 'x': $c = $left * $right; break;
1151  case '-': $c = $left - $right; break;
1152  default: $c = $left + $right; break;
1153  }
1154  } while ($c <= 0); // no negative #'s or 0
1155 
1156  $this->code = $c;
1157  $this->code_display = "$left $sign $right";
1158  break;
1159  }
1160 
1161  case self::SI_CAPTCHA_WORDS:
1162  $words = $this->readCodeFromFile(2);
1163  $this->code = implode(' ', $words);
1164  $this->code_display = $this->code;
1165  break;
1166 
1167  default:
1168  {
1169  if ($this->use_wordlist && is_readable($this->wordlist_file)) {
1170  $this->code = $this->readCodeFromFile();
1171  }
1172 
1173  if ($this->code == false) {
1174  $this->code = $this->generateCode($this->code_length);
1175  }
1176 
1177  $this->code_display = $this->code;
1178  $this->code = ($this->case_sensitive) ? $this->code : strtolower($this->code);
1179  } // default
1180  }
1181 
1182  $this->saveData();
1183  }
1184 
1188  protected function drawWord()
1189  {
1190  $width2 = $this->image_width * $this->iscale;
1191  $height2 = $this->image_height * $this->iscale;
1192 
1193  if (!is_readable($this->ttf_file)) {
1194  imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
1195  } else {
1196  if ($this->perturbation > 0) {
1197  $font_size = $height2 * .4;
1198  $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
1199  $tx = $bb[4] - $bb[0];
1200  $ty = $bb[5] - $bb[1];
1201  $x = floor($width2 / 2 - $tx / 2 - $bb[0]);
1202  $y = round($height2 / 2 - $ty / 2 - $bb[1]);
1203 
1204  imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1205  } else {
1206  $font_size = $this->image_height * .4;
1207  $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
1208  $tx = $bb[4] - $bb[0];
1209  $ty = $bb[5] - $bb[1];
1210  $x = floor($this->image_width / 2 - $tx / 2 - $bb[0]);
1211  $y = round($this->image_height / 2 - $ty / 2 - $bb[1]);
1212 
1213  imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1214  }
1215  }
1216 
1217  // DEBUG
1218  //$this->im = $this->tmpimg;
1219  //$this->output();
1220 
1221  }
1222 
1226  protected function distortedCopy()
1227  {
1228  $numpoles = 3; // distortion factor
1229  // make array of poles AKA attractor points
1230  for ($i = 0; $i < $numpoles; ++ $i) {
1231  $px[$i] = mt_rand($this->image_width * 0.2, $this->image_width * 0.8);
1232  $py[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1233  $rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1234  $tmp = ((- $this->frand()) * 0.15) - .15;
1235  $amp[$i] = $this->perturbation * $tmp;
1236  }
1237 
1238  $bgCol = imagecolorat($this->tmpimg, 0, 0);
1239  $width2 = $this->iscale * $this->image_width;
1240  $height2 = $this->iscale * $this->image_height;
1241  imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
1242  // loop over $img pixels, take pixels from $tmpimg with distortion field
1243  for ($ix = 0; $ix < $this->image_width; ++ $ix) {
1244  for ($iy = 0; $iy < $this->image_height; ++ $iy) {
1245  $x = $ix;
1246  $y = $iy;
1247  for ($i = 0; $i < $numpoles; ++ $i) {
1248  $dx = $ix - $px[$i];
1249  $dy = $iy - $py[$i];
1250  if ($dx == 0 && $dy == 0) {
1251  continue;
1252  }
1253  $r = sqrt($dx * $dx + $dy * $dy);
1254  if ($r > $rad[$i]) {
1255  continue;
1256  }
1257  $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
1258  $x += $dx * $rscale;
1259  $y += $dy * $rscale;
1260  }
1261  $c = $bgCol;
1262  $x *= $this->iscale;
1263  $y *= $this->iscale;
1264  if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) {
1265  $c = imagecolorat($this->tmpimg, $x, $y);
1266  }
1267  if ($c != $bgCol) { // only copy pixels of letters to preserve any background image
1268  imagesetpixel($this->im, $ix, $iy, $c);
1269  }
1270  }
1271  }
1272  }
1273 
1277  protected function drawLines()
1278  {
1279  for ($line = 0; $line < $this->num_lines; ++ $line) {
1280  $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
1281  $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
1282  $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
1283 
1284  $theta = ($this->frand() - 0.5) * M_PI * 0.7;
1285  $w = $this->image_width;
1286  $len = mt_rand($w * 0.4, $w * 0.7);
1287  $lwid = mt_rand(0, 2);
1288 
1289  $k = $this->frand() * 0.6 + 0.2;
1290  $k = $k * $k * 0.5;
1291  $phi = $this->frand() * 6.28;
1292  $step = 0.5;
1293  $dx = $step * cos($theta);
1294  $dy = $step * sin($theta);
1295  $n = $len / $step;
1296  $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
1297  $x0 = $x - 0.5 * $len * cos($theta);
1298  $y0 = $y - 0.5 * $len * sin($theta);
1299 
1300  $ldx = round(- $dy * $lwid);
1301  $ldy = round($dx * $lwid);
1302 
1303  for ($i = 0; $i < $n; ++ $i) {
1304  $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
1305  $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
1306  imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
1307  }
1308  }
1309  }
1310 
1314  protected function drawNoise()
1315  {
1316  if ($this->noise_level > 10) {
1317  $noise_level = 10;
1318  } else {
1320  }
1321 
1322  $t0 = microtime(true);
1323 
1324  $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
1325 
1326  $points = $this->image_width * $this->image_height * $this->iscale;
1327  $height = $this->image_height * $this->iscale;
1328  $width = $this->image_width * $this->iscale;
1329  for ($i = 0; $i < $noise_level; ++$i) {
1330  $x = mt_rand(10, $width);
1331  $y = mt_rand(10, $height);
1332  $size = mt_rand(7, 10);
1333  if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
1334  imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
1335  }
1336 
1337  $t1 = microtime(true);
1338 
1339  $t = $t1 - $t0;
1340 
1341  /*
1342  // DEBUG
1343  imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
1344  header('content-type: image/png');
1345  imagepng($this->tmpimg);
1346  exit;
1347  */
1348  }
1349 
1353  protected function addSignature()
1354  {
1355  $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
1356  $textlen = $bbox[2] - $bbox[0];
1357  $x = $this->image_width - $textlen - 5;
1358  $y = $this->image_height - 3;
1359 
1360  imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
1361  }
1362 
1366  protected function output()
1367  {
1368  if ($this->canSendHeaders() || $this->send_headers == false) {
1369  if ($this->send_headers) {
1370  // only send the content-type headers if no headers have been output
1371  // this will ease debugging on misconfigured servers where warnings
1372  // may have been output which break the image and prevent easily viewing
1373  // source to see the error.
1374  header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
1375  header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
1376  header("Cache-Control: no-store, no-cache, must-revalidate");
1377  header("Cache-Control: post-check=0, pre-check=0", false);
1378  header("Pragma: no-cache");
1379  }
1380 
1381  switch ($this->image_type) {
1382  case self::SI_IMAGE_JPEG:
1383  if ($this->send_headers) header("Content-Type: image/jpeg");
1384  imagejpeg($this->im, null, 90);
1385  break;
1386  case self::SI_IMAGE_GIF:
1387  if ($this->send_headers) header("Content-Type: image/gif");
1388  imagegif($this->im);
1389  break;
1390  default:
1391  if ($this->send_headers) header("Content-Type: image/png");
1392  imagepng($this->im);
1393  break;
1394  }
1395  } else {
1396  echo '<hr /><strong>'
1397  .'Failed to generate captcha image, content has already been '
1398  .'output.<br />This is most likely due to misconfiguration or '
1399  .'a PHP error was sent to the browser.</strong>';
1400  }
1401 
1402  imagedestroy($this->im);
1403  restore_error_handler();
1404 
1405  if (!$this->no_exit) exit;
1406  }
1407 
1413  protected function getAudibleCode()
1414  {
1415  $letters = array();
1416  $code = $this->getCode(true, true);
1417 
1418  if ($code['code'] == '') {
1419  if (strlen($this->display_value) > 0) {
1420  $code = array('code' => $this->display_value, 'display' => $this->display_value);
1421  } else {
1422  $this->createCode();
1423  $code = $this->getCode(true);
1424  }
1425  }
1426 
1427  if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) {
1428  $math = true;
1429 
1430  $left = $eq[1];
1431  $sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]);
1432  $right = $eq[3];
1433 
1434  $letters = array($left, $sign, $right);
1435  } else {
1436  $math = false;
1437 
1438  $length = strlen($code['display']);
1439 
1440  for($i = 0; $i < $length; ++$i) {
1441  $letter = $code['display']{$i};
1442  $letters[] = $letter;
1443  }
1444  }
1445 
1446  try {
1447  return $this->generateWAV($letters);
1448  } catch(Exception $ex) {
1449  throw $ex;
1450  }
1451  }
1452 
1456  protected function readCodeFromFile($numWords = 1)
1457  {
1458  $fp = fopen($this->wordlist_file, 'rb');
1459  if (!$fp) return false;
1460 
1461  $fsize = filesize($this->wordlist_file);
1462  if ($fsize < 128) return false; // too small of a list to be effective
1463 
1464  if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1;
1465 
1466  $words = array();
1467  $i = 0;
1468  do {
1469  fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
1470  $data = fread($fp, 64); // read a chunk from our random position
1471  $data = preg_replace("/\r?\n/", "\n", $data);
1472 
1473  $start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position
1474  $end = @strpos($data, "\n", $start); // find end of word
1475 
1476  if ($start === false) {
1477  // picked start position at end of file
1478  continue;
1479  } else if ($end === false) {
1480  $end = strlen($data);
1481  }
1482 
1483  $word = strtolower(substr($data, $start, $end - $start)); // return a line of the file
1484  $words[] = $word;
1485  } while (++$i < $numWords);
1486 
1487  fclose($fp);
1488 
1489  if ($numWords < 2) {
1490  return $words[0];
1491  } else {
1492  return $words;
1493  }
1494  }
1495 
1499  protected function generateCode()
1500  {
1501  $code = '';
1502 
1503  if (function_exists('mb_strlen')) {
1504  for($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i) {
1505  $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8');
1506  }
1507  } else {
1508  for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
1509  $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1);
1510  }
1511  }
1512 
1513  return $code;
1514  }
1515 
1520  protected function validate()
1521  {
1522  if (!is_string($this->code) || strlen($this->code) == 0) {
1523  $code = $this->getCode();
1524  // returns stored code, or an empty string if no stored code was found
1525  // checks the session and database if enabled
1526  } else {
1527  $code = $this->code;
1528  }
1529 
1530  if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
1531  // case sensitive was set from securimage_show.php but not in class
1532  // the code saved in the session has capitals so set case sensitive to true
1533  $this->case_sensitive = true;
1534  }
1535 
1536  $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
1537  : strtolower($this->code_entered))
1538  );
1539  $this->correct_code = false;
1540 
1541  if ($code != '') {
1542  if (strpos($code, ' ') !== false) {
1543  // for multi word captchas, remove more than once space from input
1544  $code_entered = preg_replace('/\s+/', ' ', $code_entered);
1545  $code_entered = strtolower($code_entered);
1546  }
1547 
1548  if ($code == $code_entered) {
1549  $this->correct_code = true;
1550  if ($this->no_session != true) {
1551  $_SESSION['securimage_code_value'][$this->namespace] = '';
1552  $_SESSION['securimage_code_ctime'][$this->namespace] = '';
1553  }
1554  $this->clearCodeFromDatabase();
1555  }
1556  }
1557  }
1558 
1562  protected function saveData()
1563  {
1564  if ($this->no_session != true) {
1565  if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) {
1566  // fix for migration from v2 - v3
1567  unset($_SESSION['securimage_code_value']);
1568  unset($_SESSION['securimage_code_ctime']);
1569  }
1570 
1571  $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display;
1572  $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
1573  $_SESSION['securimage_code_ctime'][$this->namespace] = time();
1574  }
1575 
1576  if ($this->use_database) {
1577  $this->saveCodeToDatabase();
1578  }
1579  }
1580 
1584  protected function saveCodeToDatabase()
1585  {
1586  $success = false;
1587  $this->openDatabase();
1588 
1589  if ($this->use_database && $this->pdo_conn) {
1590  $id = $this->getCaptchaId(false);
1591  $ip = $_SERVER['REMOTE_ADDR'];
1592 
1593  if (empty($id)) {
1594  $id = $ip;
1595  }
1596 
1597  $time = time();
1598  $code = $this->code;
1599  $code_disp = $this->code_display;
1600 
1601  // This is somewhat expensive in PDO Sqlite3 (when there is something to delete)
1602  $this->clearCodeFromDatabase();
1603 
1604  $query = "INSERT INTO {$this->database_table} ("
1605  ."id, code, code_display, namespace, created) "
1606  ."VALUES(?, ?, ?, ?, ?)";
1607 
1608  $stmt = $this->pdo_conn->prepare($query);
1609  $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time));
1610 
1611  if (!$success) {
1612  $err = $stmt->errorInfo();
1613  trigger_error("Failed to insert code into database. {$err[1]}: {$err[2]}", E_USER_WARNING);
1614  }
1615  }
1616 
1617  return $success !== false;
1618  }
1619 
1623  protected function openDatabase()
1624  {
1625  $this->pdo_conn = false;
1626 
1627  if ($this->use_database) {
1628  $pdo_extension = 'PDO_' . strtoupper($this->database_driver);
1629 
1630  if (!extension_loaded($pdo_extension)) {
1631  trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING);
1632  return false;
1633  }
1634  }
1635 
1636  if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
1637  if (!file_exists($this->database_file)) {
1638  $fp = fopen($this->database_file, 'w+');
1639  if (!$fp) {
1640  $err = error_get_last();
1641  trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING);
1642  return false;
1643  }
1644  fclose($fp);
1645  chmod($this->database_file, 0666);
1646  } else if (!is_writeable($this->database_file)) {
1647  trigger_error("Securimage does not have read/write access to database file '{$this->database_file}. Make sure permissions are 0666 and writeable by user '" . get_current_user() . "'", E_USER_WARNING);
1648  return false;
1649  }
1650  }
1651 
1652  $dsn = $this->getDsn();
1653 
1654  try {
1655  $options = array();
1656  $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options);
1657  } catch (PDOException $pdoex) {
1658  trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING);
1659  return false;
1660  }
1661 
1662  try {
1663  if (!$this->checkTablesExist()) {
1664  // create tables...
1665  $this->createDatabaseTables();
1666  }
1667  } catch (Exception $ex) {
1668  trigger_error($ex->getMessage(), E_USER_WARNING);
1669  $this->pdo_conn = null;
1670  return false;
1671  }
1672 
1673  if (mt_rand(0, 100) / 100.0 == 1.0) {
1674  $this->purgeOldCodesFromDatabase();
1675  }
1676 
1677  return $this->pdo_conn;
1678  }
1679 
1680  protected function getDsn()
1681  {
1682  $dsn = sprintf('%s:', $this->database_driver);
1683 
1684  switch($this->database_driver) {
1685  case self::SI_DRIVER_SQLITE3:
1686  $dsn .= $this->database_file;
1687  break;
1688 
1689  case self::SI_DRIVER_MYSQL:
1690  case self::SI_DRIVER_PGSQL:
1691  $dsn .= sprintf('host=%s;dbname=%s',
1692  $this->database_host,
1693  $this->database_name);
1694  break;
1695 
1696  }
1697 
1698  return $dsn;
1699  }
1700 
1701  protected function checkTablesExist()
1702  {
1703  $table = $this->pdo_conn->quote($this->database_table);
1704 
1705  switch($this->database_driver) {
1706  case self::SI_DRIVER_SQLITE3:
1707  // query row count for sqlite, PRAGMA queries seem to return no
1708  // rowCount using PDO even if there are rows returned
1709  $query = "SELECT COUNT(id) FROM $table";
1710  break;
1711 
1712  case self::SI_DRIVER_MYSQL:
1713  $query = "SHOW TABLES LIKE $table";
1714  break;
1715 
1716  case self::SI_DRIVER_PGSQL:
1717  $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;";
1718  break;
1719  }
1720 
1721  $result = $this->pdo_conn->query($query);
1722 
1723  if (!$result) {
1724  $err = $this->pdo_conn->errorInfo();
1725 
1726  if ($this->database_driver == self::SI_DRIVER_SQLITE3 &&
1727  $err[1] === 1 && strpos($err[2], 'no such table') !== false)
1728  {
1729  return false;
1730  }
1731 
1732  throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}");
1733  } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
1734  // successful here regardless of row count for sqlite
1735  return true;
1736  } else if ($result->rowCount() == 0) {
1737  return false;
1738  } else {
1739  return true;
1740  }
1741  }
1742 
1743  protected function createDatabaseTables()
1744  {
1745  $queries = array();
1746 
1747  switch($this->database_driver) {
1748  case self::SI_DRIVER_SQLITE3:
1749  $queries[] = "CREATE TABLE \"{$this->database_table}\" (
1750  id VARCHAR(40),
1751  namespace VARCHAR(32) NOT NULL,
1752  code VARCHAR(32) NOT NULL,
1753  code_display VARCHAR(32) NOT NULL,
1754  created INTEGER NOT NULL,
1755  PRIMARY KEY(id, namespace)
1756  )";
1757 
1758  $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)";
1759  break;
1760 
1761  case self::SI_DRIVER_MYSQL:
1762  $queries[] = "CREATE TABLE `{$this->database_table}` (
1763  `id` VARCHAR(40) NOT NULL,
1764  `namespace` VARCHAR(32) NOT NULL,
1765  `code` VARCHAR(32) NOT NULL,
1766  `code_display` VARCHAR(32) NOT NULL,
1767  `created` INT NOT NULL,
1768  PRIMARY KEY(id, namespace),
1769  INDEX(created)
1770  )";
1771  break;
1772 
1773  case self::SI_DRIVER_PGSQL:
1774  $queries[] = "CREATE TABLE {$this->database_table} (
1775  id character varying(40) NOT NULL,
1776  namespace character varying(32) NOT NULL,
1777  code character varying(32) NOT NULL,
1778  code_display character varying(32) NOT NULL,
1779  created integer NOT NULL,
1780  CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace)
1781  )";
1782 
1783  $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);";
1784  break;
1785  }
1786 
1787  $this->pdo_conn->beginTransaction();
1788 
1789  foreach($queries as $query) {
1790  $result = $this->pdo_conn->query($query);
1791 
1792  if (!$result) {
1793  $err = $this->pdo_conn->errorInfo();
1794  trigger_error("Failed to create table. {$err[1]}: {$err[2]}", E_USER_WARNING);
1795  $this->pdo_conn->rollBack();
1796  $this->pdo_conn = false;
1797  return false;
1798  }
1799  }
1800 
1801  $this->pdo_conn->commit();
1802 
1803  return true;
1804  }
1805 
1813  protected function getCodeFromDatabase()
1814  {
1815  $code = '';
1816 
1817  if ($this->use_database == true && $this->pdo_conn) {
1818  if (Securimage::$_captchaId !== null) {
1819  $query = "SELECT * FROM {$this->database_table} WHERE id = ?";
1820  $stmt = $this->pdo_conn->prepare($query);
1821  $result = $stmt->execute(array(Securimage::$_captchaId));
1822  } else {
1823  $ip = $_SERVER['REMOTE_ADDR'];
1824  $ns = $this->namespace;
1825 
1826  // ip is stored in id column when no captchaId
1827  $query = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?";
1828  $stmt = $this->pdo_conn->prepare($query);
1829  $result = $stmt->execute(array($ip, $ns));
1830  }
1831 
1832  if (!$result) {
1833  $err = $this->pdo_conn->errorInfo();
1834  trigger_error("Failed to select code from database. {$err[0]}: {$err[1]}", E_USER_WARNING);
1835  } else {
1836  if ( ($row = $stmt->fetch()) !== false ) {
1837  if (false == $this->isCodeExpired($row['created'])) {
1838  if (Securimage::$_captchaId !== null) {
1839  // return an array when using captchaId
1840  $code = array('code' => $row['code'],
1841  'code_disp' => $row['code_display']);
1842  } else {
1843  $code = $row['code'];
1844  }
1845  }
1846  }
1847  }
1848  }
1849 
1850  return $code;
1851  }
1852 
1856  protected function clearCodeFromDatabase()
1857  {
1858  if ($this->pdo_conn) {
1859  $ip = $_SERVER['REMOTE_ADDR'];
1860  $ns = $this->pdo_conn->quote($this->namespace);
1862 
1863  if (empty($id)) {
1864  $id = $ip; // if no captchaId set, IP address is captchaId.
1865  }
1866 
1867  $id = $this->pdo_conn->quote($id);
1868 
1869  $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s",
1870  $this->database_table, $id, $ns);
1871 
1872  $result = $this->pdo_conn->query($query);
1873  if (!$result) {
1874  trigger_error("Failed to delete code from database.", E_USER_WARNING);
1875  }
1876  }
1877  }
1878 
1882  protected function purgeOldCodesFromDatabase()
1883  {
1884  if ($this->use_database && $this->pdo_conn) {
1885  $now = time();
1886  $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
1887 
1888  $query = sprintf("DELETE FROM %s WHERE %s - created > %s",
1889  $this->database_table,
1890  $this->pdo_conn->quote($now, PDO::PARAM_INT),
1891  $this->pdo_conn->quote($limit, PDO::PARAM_INT));
1892 
1893  $result = $this->pdo_conn->query($query);
1894  }
1895  }
1896 
1901  protected function isCodeExpired($creation_time)
1902  {
1903  $expired = true;
1904 
1905  if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
1906  $expired = false;
1907  } else if (time() - $creation_time < $this->expiry_time) {
1908  $expired = false;
1909  }
1910 
1911  return $expired;
1912  }
1913 
1920  protected function generateWAV($letters)
1921  {
1922  $wavCaptcha = new WavFile();
1923  $first = true; // reading first wav file
1924 
1925  foreach ($letters as $letter) {
1926  $letter = strtoupper($letter);
1927 
1928  try {
1929  $l = new WavFile($this->audio_path . '/' . $letter . '.wav');
1930 
1931  if ($first) {
1932  // set sample rate, bits/sample, and # of channels for file based on first letter
1933  $wavCaptcha->setSampleRate($l->getSampleRate())
1934  ->setBitsPerSample($l->getBitsPerSample())
1935  ->setNumChannels($l->getNumChannels());
1936  $first = false;
1937  }
1938 
1939  // append letter to the captcha audio
1940  $wavCaptcha->appendWav($l);
1941 
1942  // random length of silence between $audio_gap_min and $audio_gap_max
1943  if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) {
1944  $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 );
1945  }
1946  } catch (Exception $ex) {
1947  // failed to open file, or the wav file is broken or not supported
1948  // 2 wav files were not compatible, different # channels, bits/sample, or sample rate
1949  throw $ex;
1950  }
1951  }
1952 
1953  /********* Set up audio filters *****************************/
1954  $filters = array();
1955 
1956  if ($this->audio_use_noise == true) {
1957  // use background audio - find random file
1958  $noiseFile = $this->getRandomNoiseFile();
1959 
1960  if ($noiseFile !== false && is_readable($noiseFile)) {
1961  try {
1962  $wavNoise = new WavFile($noiseFile, false);
1963  } catch(Exception $ex) {
1964  throw $ex;
1965  }
1966 
1967  // start at a random offset from the beginning of the wavfile
1968  // in order to add more randomness
1969  $randOffset = 0;
1970  if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) {
1971  $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks());
1972  $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign());
1973  } else {
1974  $wavNoise->readWavData();
1975  $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1);
1976  }
1977 
1978 
1979  $mixOpts = array('wav' => $wavNoise,
1980  'loop' => true,
1981  'blockOffset' => $randOffset);
1982 
1983  $filters[WavFile::FILTER_MIX] = $mixOpts;
1985  }
1986  }
1987 
1988  if ($this->degrade_audio == true) {
1989  // add random noise.
1990  // any noise level below 95% is intensely distorted and not pleasant to the ear
1991  $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0;
1992  }
1993 
1994  if (!empty($filters)) {
1995  $wavCaptcha->filter($filters); // apply filters to captcha audio
1996  }
1997 
1998  return $wavCaptcha->__toString();
1999  }
2000 
2001  public function getRandomNoiseFile()
2002  {
2003  $return = false;
2004 
2005  if ( ($dh = opendir($this->audio_noise_path)) !== false ) {
2006  $list = array();
2007 
2008  while ( ($file = readdir($dh)) !== false ) {
2009  if ($file == '.' || $file == '..') continue;
2010  if (strtolower(substr($file, -4)) != '.wav') continue;
2011 
2012  $list[] = $file;
2013  }
2014 
2015  closedir($dh);
2016 
2017  if (sizeof($list) > 0) {
2018  $file = $list[array_rand($list, 1)];
2019  $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file;
2020  }
2021  }
2022 
2023  return $return;
2024  }
2025 
2031  protected function audioError()
2032  {
2033  return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav');
2034  }
2035 
2041  protected function canSendHeaders()
2042  {
2043  if (headers_sent()) {
2044  // output has been flushed and headers have already been sent
2045  return false;
2046  } else if (strlen((string)ob_get_contents()) > 0) {
2047  // headers haven't been sent, but there is data in the buffer that will break image and audio data
2048  return false;
2049  }
2050 
2051  return true;
2052  }
2053 
2059  function frand()
2060  {
2061  return 0.0001 * mt_rand(0,9999);
2062  }
2063 
2069  protected function initColor($color, $default)
2070  {
2071  if ($color == null) {
2072  return new Securimage_Color($default);
2073  } else if (is_string($color)) {
2074  try {
2075  return new Securimage_Color($color);
2076  } catch(Exception $e) {
2077  return new Securimage_Color($default);
2078  }
2079  } else if (is_array($color) && sizeof($color) == 3) {
2080  return new Securimage_Color($color[0], $color[1], $color[2]);
2081  } else {
2082  return new Securimage_Color($default);
2083  }
2084  }
2085 
2101  public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array())
2102  {
2103  // get the current error reporting level
2104  $level = error_reporting();
2105 
2106  // if error was supressed or $errno not set in current error level
2107  if ($level == 0 || ($level & $errno) == 0) {
2108  return true;
2109  }
2110 
2111  return false;
2112  }
2113 }
2114 
2115 
2126 {
2127  public $r;
2128  public $g;
2129  public $b;
2130 
2142  public function __construct($color = '#ffffff')
2143  {
2144  $args = func_get_args();
2145 
2146  if (sizeof($args) == 0) {
2147  $this->r = 255;
2148  $this->g = 255;
2149  $this->b = 255;
2150  } else if (sizeof($args) == 1) {
2151  // set based on html code
2152  if (substr($color, 0, 1) == '#') {
2153  $color = substr($color, 1);
2154  }
2155 
2156  if (strlen($color) != 3 && strlen($color) != 6) {
2157  throw new InvalidArgumentException(
2158  'Invalid HTML color code passed to Securimage_Color'
2159  );
2160  }
2161 
2162  $this->constructHTML($color);
2163  } else if (sizeof($args) == 3) {
2164  $this->constructRGB($args[0], $args[1], $args[2]);
2165  } else {
2166  throw new InvalidArgumentException(
2167  'Securimage_Color constructor expects 0, 1 or 3 arguments; ' . sizeof($args) . ' given'
2168  );
2169  }
2170  }
2171 
2178  protected function constructRGB($red, $green, $blue)
2179  {
2180  if ($red < 0) $red = 0;
2181  if ($red > 255) $red = 255;
2182  if ($green < 0) $green = 0;
2183  if ($green > 255) $green = 255;
2184  if ($blue < 0) $blue = 0;
2185  if ($blue > 255) $blue = 255;
2186 
2187  $this->r = $red;
2188  $this->g = $green;
2189  $this->b = $blue;
2190  }
2191 
2196  protected function constructHTML($color)
2197  {
2198  if (strlen($color) == 3) {
2199  $red = str_repeat(substr($color, 0, 1), 2);
2200  $green = str_repeat(substr($color, 1, 1), 2);
2201  $blue = str_repeat(substr($color, 2, 1), 2);
2202  } else {
2203  $red = substr($color, 0, 2);
2204  $green = substr($color, 2, 2);
2205  $blue = substr($color, 4, 2);
2206  }
2207 
2208  $this->r = hexdec($red);
2209  $this->g = hexdec($green);
2210  $this->b = hexdec($blue);
2211  }
2212 }