im); break; default: if ($this->send_headers) header("Content-Type: image/png"); imagepng($this->im); break; } } else { echo '
' .'Failed to generate captcha image, content has already been ' .'output.
This is most likely due to misconfiguration or ' .'a PHP error was sent to the browser.
'; } imagedestroy($this->im); restore_error_handler(); if (!$this->no_exit) exit; } /** * Generates an audio captcha in WAV format * * @return string The audio representation of the captcha in Wav format */ protected function getAudibleCode() { $letters = array(); $code = $this->getCode(true, true); if (empty($code) || $code['code'] == '') { if (strlen($this->display_value) > 0) { $code = array('code' => $this->display_value, 'display' => $this->display_value); } else { $this->createCode(); $code = $this->getCode(true); } } if (empty($code)) { $error = 'Failed to get audible code (are database settings correct?). Check the error log for details'; trigger_error($error, E_USER_WARNING); throw new Exception($error); } if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) { $math = true; $left = $eq[1]; $sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]); $right = $eq[3]; $letters = array($left, $sign, $right); } else { $math = false; $length = strlen($code['display']); for($i = 0; $i < $length; ++$i) { $letter = $code['display']{$i}; $letters[] = $letter; } } try { return $this->generateWAV($letters); } catch(Exception $ex) { throw $ex; } } /** * Gets a captcha code from a file containing a list of words. * * Seek to a random offset in the file and reads a block of data and returns a line from the file. * * @param int $numWords Number of words (lines) to read from the file * @return string|array Returns a string if only one word is to be read, or an array of words */ protected function readCodeFromFile($numWords = 1) { $fp = fopen($this->wordlist_file, 'rb'); if (!$fp) return false; $fsize = filesize($this->wordlist_file); if ($fsize < 128) return false; // too small of a list to be effective if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1; $words = array(); $i = 0; do { fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64 $data = fread($fp, 64); // read a chunk from our random position $data = preg_replace("/\r?\n/", "\n", $data); $start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position $end = @strpos($data, "\n", $start); // find end of word if ($start === false) { // picked start position at end of file continue; } else if ($end === false) { $end = strlen($data); } $word = strtolower(substr($data, $start, $end - $start)); // return a line of the file $words[] = $word; } while (++$i < $numWords); fclose($fp); if ($numWords < 2) { return $words[0]; } else { return $words; } } /** * Generates a random captcha code from the set character set * * @see Securimage::$charset Charset option * @return string A randomly generated CAPTCHA code */ protected function generateCode() { $code = ''; if (function_exists('mb_strlen')) { for($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i) { $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8'); } } else { for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) { $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1); } } return $code; } /** * Validate a code supplied by the user * * Checks the entered code against the value stored in the session and/or database (if configured). Handles case sensitivity. * Also removes the code from session/database if the code was entered correctly to prevent re-use attack. * * This function does not return a value. * * @see Securimage::$correct_code 'correct_code' property */ protected function validate() { if (!is_string($this->code) || strlen($this->code) == 0) { $code = $this->getCode(true); // returns stored code, or an empty string if no stored code was found // checks the session and database if enabled } else { $code = $this->code; } if (is_array($code)) { if (!empty($code)) { $ctime = $code['time']; $code = $code['code']; $this->_timeToSolve = time() - $ctime; } else { $code = ''; } } if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) { // case sensitive was set from securimage_show.php but not in class // the code saved in the session has capitals so set case sensitive to true $this->case_sensitive = true; } $code_entered = trim( (($this->case_sensitive) ? $this->code_entered : strtolower($this->code_entered)) ); $this->correct_code = false; if ($code != '') { if (strpos($code, ' ') !== false) { // for multi word captchas, remove more than once space from input $code_entered = preg_replace('/\s+/', ' ', $code_entered); $code_entered = strtolower($code_entered); } if ($code == $code_entered) { $this->correct_code = true; if ($this->no_session != true) { $_SESSION['securimage_code_disp'] [$this->namespace] = ''; $_SESSION['securimage_code_value'][$this->namespace] = ''; $_SESSION['securimage_code_ctime'][$this->namespace] = ''; } $this->clearCodeFromDatabase(); } } } /** * Save CAPTCHA data to session and database (if configured) */ protected function saveData() { if ($this->no_session != true) { if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) { // fix for migration from v2 - v3 unset($_SESSION['securimage_code_value']); unset($_SESSION['securimage_code_ctime']); } $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display; $_SESSION['securimage_code_value'][$this->namespace] = $this->code; $_SESSION['securimage_code_ctime'][$this->namespace] = time(); } if ($this->use_database) { $this->saveCodeToDatabase(); } } /** * Saves the CAPTCHA data to the configured database. */ protected function saveCodeToDatabase() { $success = false; $this->openDatabase(); if ($this->use_database && $this->pdo_conn) { $id = $this->getCaptchaId(false); $ip = $_SERVER['REMOTE_ADDR']; if (empty($id)) { $id = $ip; } $time = time(); $code = $this->code; $code_disp = $this->code_display; // This is somewhat expensive in PDO Sqlite3 (when there is something to delete) $this->clearCodeFromDatabase(); $query = "INSERT INTO {$this->database_table} (" ."id, code, code_display, namespace, created) " ."VALUES(?, ?, ?, ?, ?)"; $stmt = $this->pdo_conn->prepare($query); $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time)); if (!$success) { $err = $stmt->errorInfo(); $error = "Failed to insert code into database. {$err[1]}: {$err[2]}."; if ($this->database_driver == self::SI_DRIVER_SQLITE3) { $err14 = ($err[1] == 14); if ($err14) $error .= sprintf(" Ensure database directory and file are writeable by user '%s' (%d).", get_current_user(), getmyuid()); } trigger_error($error, E_USER_WARNING); } } return $success !== false; } /** * Opens a connection to the configured database. * * @see Securimage::$use_database Use database * @see Securimage::$database_driver Database driver * @see Securimage::$pdo_conn pdo_conn * @return bool true if the database connection was successful, false if not */ protected function openDatabase() { $this->pdo_conn = false; if ($this->use_database) { $pdo_extension = 'PDO_' . strtoupper($this->database_driver); if (!extension_loaded($pdo_extension)) { trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING); return false; } } if ($this->database_driver == self::SI_DRIVER_SQLITE3) { if (!file_exists($this->database_file)) { $fp = fopen($this->database_file, 'w+'); if (!$fp) { $err = error_get_last(); trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING); return false; } fclose($fp); chmod($this->database_file, 0666); } else if (!is_writeable($this->database_file)) { 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); return false; } } try { $dsn = $this->getDsn(); $options = array(); $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options); } catch (PDOException $pdoex) { trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING); return false; } catch (Exception $ex) { trigger_error($ex->getMessage(), E_USER_WARNING); return false; } try { if (!$this->skip_table_check && !$this->checkTablesExist()) { // create tables... $this->createDatabaseTables(); } } catch (Exception $ex) { trigger_error($ex->getMessage(), E_USER_WARNING); $this->pdo_conn = null; return false; } if (mt_rand(0, 100) / 100.0 == 1.0) { $this->purgeOldCodesFromDatabase(); } return $this->pdo_conn; } /** * Get the PDO DSN string for connecting to the database * * @see Securimage::$database_driver Database driver * @throws Exception If database specific options are not configured * @return string The DSN for connecting to the database */ protected function getDsn() { $dsn = sprintf('%s:', $this->database_driver); switch($this->database_driver) { case self::SI_DRIVER_SQLITE3: $dsn .= $this->database_file; break; case self::SI_DRIVER_MYSQL: case self::SI_DRIVER_PGSQL: if (empty($this->database_host)) { throw new Exception('Securimage::database_host is not set'); } else if (empty($this->database_name)) { throw new Exception('Securimage::database_name is not set'); } $dsn .= sprintf('host=%s;dbname=%s', $this->database_host, $this->database_name); break; } return $dsn; } /** * Checks if the necessary database tables for storing captcha codes exist * * @throws Exception If the table check failed for some reason * @return boolean true if the database do exist, false if not */ protected function checkTablesExist() { $table = $this->pdo_conn->quote($this->database_table); switch($this->database_driver) { case self::SI_DRIVER_SQLITE3: // query row count for sqlite, PRAGMA queries seem to return no // rowCount using PDO even if there are rows returned $query = "SELECT COUNT(id) FROM $table"; break; case self::SI_DRIVER_MYSQL: $query = "SHOW TABLES LIKE $table"; break; case self::SI_DRIVER_PGSQL: $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;"; break; } $result = $this->pdo_conn->query($query); if (!$result) { $err = $this->pdo_conn->errorInfo(); if ($this->database_driver == self::SI_DRIVER_SQLITE3 && $err[1] === 1 && strpos($err[2], 'no such table') !== false) { return false; } throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}"); } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) { // successful here regardless of row count for sqlite return true; } else if ($result->rowCount() == 0) { return false; } else { return true; } } /** * Create the necessary databaes table for storing captcha codes. * * Based on the database adapter used, the tables will created in the existing connection. * * @see Securimage::$database_driver Database driver * @return boolean true if the tables were created, false if not */ protected function createDatabaseTables() { $queries = array(); switch($this->database_driver) { case self::SI_DRIVER_SQLITE3: $queries[] = "CREATE TABLE \"{$this->database_table}\" ( id VARCHAR(40), namespace VARCHAR(32) NOT NULL, code VARCHAR(32) NOT NULL, code_display VARCHAR(32) NOT NULL, created INTEGER NOT NULL, PRIMARY KEY(id, namespace) )"; $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)"; break; case self::SI_DRIVER_MYSQL: $queries[] = "CREATE TABLE `{$this->database_table}` ( `id` VARCHAR(40) NOT NULL, `namespace` VARCHAR(32) NOT NULL, `code` VARCHAR(32) NOT NULL, `code_display` VARCHAR(32) NOT NULL, `created` INT NOT NULL, PRIMARY KEY(id, namespace), INDEX(created) )"; break; case self::SI_DRIVER_PGSQL: $queries[] = "CREATE TABLE {$this->database_table} ( id character varying(40) NOT NULL, namespace character varying(32) NOT NULL, code character varying(32) NOT NULL, code_display character varying(32) NOT NULL, created integer NOT NULL, CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace) )"; $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);"; break; } $this->pdo_conn->beginTransaction(); foreach($queries as $query) { $result = $this->pdo_conn->query($query); if (!$result) { $err = $this->pdo_conn->errorInfo(); trigger_error("Failed to create table. {$err[1]}: {$err[2]}", E_USER_WARNING); $this->pdo_conn->rollBack(); $this->pdo_conn = false; return false; } } $this->pdo_conn->commit(); return true; } /** * Retrieves a stored code from the database for based on the captchaId or * IP address if captcha ID not used. * * @return string|array Empty string if no code was found or has expired, * otherwise returns array of code information. */ protected function getCodeFromDatabase() { $code = ''; if ($this->use_database == true && $this->pdo_conn) { if (Securimage::$_captchaId !== null) { $query = "SELECT * FROM {$this->database_table} WHERE id = ?"; $stmt = $this->pdo_conn->prepare($query); $result = $stmt->execute(array(Securimage::$_captchaId)); } else { $ip = $_SERVER['REMOTE_ADDR']; $ns = $this->namespace; // ip is stored in id column when no captchaId $query = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?"; $stmt = $this->pdo_conn->prepare($query); $result = $stmt->execute(array($ip, $ns)); } if (!$result) { $err = $this->pdo_conn->errorInfo(); trigger_error("Failed to select code from database. {$err[0]}: {$err[1]}", E_USER_WARNING); } else { if ( ($row = $stmt->fetch()) !== false ) { if (false == $this->isCodeExpired($row['created'])) { $code = array( 'code' => $row['code'], 'code_disp' => $row['code_display'], 'time' => $row['created'], ); } } } } return $code; } /** * Remove a stored code from the database based on captchaId or IP address. */ protected function clearCodeFromDatabase() { if ($this->pdo_conn) { $ip = $_SERVER['REMOTE_ADDR']; $ns = $this->pdo_conn->quote($this->namespace); $id = Securimage::$_captchaId; if (empty($id)) { $id = $ip; // if no captchaId set, IP address is captchaId. } $id = $this->pdo_conn->quote($id); $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s", $this->database_table, $id, $ns); $result = $this->pdo_conn->query($query); if (!$result) { trigger_error("Failed to delete code from database.", E_USER_WARNING); } } } /** * Deletes old (expired) codes from the database */ protected function purgeOldCodesFromDatabase() { if ($this->use_database && $this->pdo_conn) { $now = time(); $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time; $query = sprintf("DELETE FROM %s WHERE %s - created > %s", $this->database_table, $this->pdo_conn->quote($now, PDO::PARAM_INT), $this->pdo_conn->quote($limit, PDO::PARAM_INT)); $result = $this->pdo_conn->query($query); } } /** * Checks to see if the captcha code has expired and can no longer be used. * * @see Securimage::$expiry_time expiry_time * @param int $creation_time The Unix timestamp of when the captcha code was created * @return bool true if the code is expired, false if it is still valid */ protected function isCodeExpired($creation_time) { $expired = true; if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) { $expired = false; } else if (time() - $creation_time < $this->expiry_time) { $expired = false; } return $expired; } /** * Generate a wav file given the $letters in the code * * @param array $letters The letters making up the captcha * @return string The audio content in WAV format */ protected function generateWAV($letters) { $wavCaptcha = new WavFile(); $first = true; // reading first wav file if ($this->audio_use_sox && !is_executable($this->sox_binary_path)) { throw new Exception("Path to SoX binary is incorrect or not executable"); } foreach ($letters as $letter) { $letter = strtoupper($letter); try { $letter_file = realpath($this->audio_path) . DIRECTORY_SEPARATOR . $letter . '.wav'; if ($this->audio_use_sox) { $sox_cmd = sprintf("%s %s -t wav - %s", $this->sox_binary_path, $letter_file, $this->getSoxEffectChain()); $data = `$sox_cmd`; $l = new WavFile(); $l->setIgnoreChunkSizes(true); $l->setWavData($data); } else { $l = new WavFile($letter_file); } if ($first) { // set sample rate, bits/sample, and # of channels for file based on first letter $wavCaptcha->setSampleRate($l->getSampleRate()) ->setBitsPerSample($l->getBitsPerSample()) ->setNumChannels($l->getNumChannels()); $first = false; } // append letter to the captcha audio $wavCaptcha->appendWav($l); // random length of silence between $audio_gap_min and $audio_gap_max if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) { $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 ); } } catch (Exception $ex) { // failed to open file, or the wav file is broken or not supported // 2 wav files were not compatible, different # channels, bits/sample, or sample rate throw new Exception("Error generating audio captcha on letter '$letter': " . $ex->getMessage()); } } /********* Set up audio filters *****************************/ $filters = array(); if ($this->audio_use_noise == true) { // use background audio - find random file $wavNoise = false; $randOffset = 0; /* // uncomment to try experimental SoX noise generation. // warning: sounds may be considered annoying if ($this->audio_use_sox) { $duration = $wavCaptcha->getDataSize() / ($wavCaptcha->getBitsPerSample() / 8) / $wavCaptcha->getNumChannels() / $wavCaptcha->getSampleRate(); $duration = round($duration, 2); $wavNoise = new WavFile(); $wavNoise->setIgnoreChunkSizes(true); $noiseData = $this->getSoxNoiseData($duration, $wavCaptcha->getNumChannels(), $wavCaptcha->getSampleRate(), $wavCaptcha->getBitsPerSample()); $wavNoise->setWavData($noiseData, true); } else */ if ( ($noiseFile = $this->getRandomNoiseFile()) !== false) { try { $wavNoise = new WavFile($noiseFile, false); } catch(Exception $ex) { throw $ex; } // start at a random offset from the beginning of the wavfile // in order to add more randomness $randOffset = 0; if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) { $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks()); $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign()); } else { $wavNoise->readWavData(); $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1); } } if ($wavNoise !== false) { $mixOpts = array('wav' => $wavNoise, 'loop' => true, 'blockOffset' => $randOffset); $filters[WavFile::FILTER_MIX] = $mixOpts; $filters[WavFile::FILTER_NORMALIZE] = $this->audio_mix_normalization; } } if ($this->degrade_audio == true) { // add random noise. // any noise level below 95% is intensely distorted and not pleasant to the ear $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0; } if (!empty($filters)) { $wavCaptcha->filter($filters); // apply filters to captcha audio } return $wavCaptcha->__toString(); } /** * Gets and returns the path to a random noise file from the audio noise directory. * * @return bool|string false if a file could not be found, or a string containing the path to the file. */ public function getRandomNoiseFile() { $return = false; if ( ($dh = opendir($this->audio_noise_path)) !== false ) { $list = array(); while ( ($file = readdir($dh)) !== false ) { if ($file == '.' || $file == '..') continue; if (strtolower(substr($file, -4)) != '.wav') continue; $list[] = $file; } closedir($dh); if (sizeof($list) > 0) { $file = $list[array_rand($list, 1)]; $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file; if (!is_readable($return)) $return = false; } } return $return; } /** * Get a random effect or chain of effects to apply to a segment of the * audio file. * * These effects should increase the randomness of the audio for * a particular letter/number by modulating the signal. The SoX effects * used are *bend*, *chorus*, *overdrive*, *pitch*, *reverb*, *tempo*, and * *tremolo*. * * For each effect selected, random parameters are supplied to the effect. * * @param int $numEffects How many effects to chain together * @return string A string of valid SoX effects and their respective options. */ protected function getSoxEffectChain($numEffects = 2) { $effectsList = array('bend', 'chorus', 'overdrive', 'pitch', 'reverb', 'tempo', 'tremolo'); $effects = array_rand($effectsList, $numEffects); $outEffects = array(); if (!is_array($effects)) $effects = array($effects); foreach($effects as $effect) { $effect = $effectsList[$effect]; switch($effect) { case 'bend': $delay = mt_rand(0, 15) / 100.0; $cents = mt_rand(-120, 120); $dur = mt_rand(75, 400) / 100.0; $outEffects[] = "$effect $delay,$cents,$dur"; break; case 'chorus': $gainIn = mt_rand(75, 90) / 100.0; $gainOut = mt_rand(70, 95) / 100.0; $chorStr = "$effect $gainIn $gainOut"; for ($i = 0; $i < mt_rand(2, 3); ++$i) { $delay = mt_rand(20, 100); $decay = mt_rand(10, 100) / 100.0; $speed = mt_rand(20, 50) / 100.0; $depth = mt_rand(150, 250) / 100.0; $chorStr .= " $delay $decay $speed $depth -s"; } $outEffects[] = $chorStr; break; case 'overdrive': $gain = mt_rand(5, 25); $color = mt_rand(20, 70); $outEffects[] = "$effect $gain $color"; break; case 'pitch': $cents = mt_rand(-300, 300); $outEffects[] = "$effect $cents"; break; case 'reverb': $reverberance = mt_rand(20, 80); $damping = mt_rand(10, 80); $scale = mt_rand(85, 100); $depth = mt_rand(90, 100); $predelay = mt_rand(0, 5); $outEffects[] = "$effect $reverberance $damping $scale $depth $predelay"; break; case 'tempo': $factor = mt_rand(65, 135) / 100.0; $outEffects[] = "$effect -s $factor"; break; case 'tremolo': $hz = mt_rand(10, 30); $depth = mt_rand(40, 85); $outEffects[] = "$effect $hz $depth"; break; } } return implode(' ', $outEffects); } /** * This function is not yet used. * * Generate random background noise from sweeping oscillators * * @param float $duration How long in seconds the generated sound will be * @param int $numChannels Number of channels in output wav * @param int $sampleRate Sample rate of output wav * @param int $bitRate Bits per sample (8, 16, 24) * @return string Audio data in wav format */ protected function getSoxNoiseData($duration, $numChannels, $sampleRate, $bitRate) { $shapes = array('sine', 'square', 'triangle', 'sawtooth', 'trapezium'); $steps = array(':', '+', '/', '-'); $selShapes = array_rand($shapes, 2); $selSteps = array_rand($steps, 2); $sweep0 = array(); $sweep0[0] = mt_rand(100, 700); $sweep0[1] = mt_rand(1500, 2500); $sweep1 = array(); $sweep1[0] = mt_rand(500, 1000); $sweep1[1] = mt_rand(1200, 2000); if (mt_rand(0, 10) % 2 == 0) $sweep0 = array_reverse($sweep0); if (mt_rand(0, 10) % 2 == 0) $sweep1 = array_reverse($sweep1); $cmd = sprintf("%s -c %d -r %d -b %d -n -t wav - synth noise create vol 0.3 synth %.2f %s mix %d%s%d vol 0.3 synth %.2f %s fmod %d%s%d vol 0.3", $this->sox_binary_path, $numChannels, $sampleRate, $bitRate, $duration, $shapes[$selShapes[0]], $sweep0[0], $steps[$selSteps[0]], $sweep0[1], $duration, $shapes[$selShapes[1]], $sweep1[0], $steps[$selSteps[1]], $sweep1[1] ); $data = `$cmd`; return $data; } /** * Return a wav file saying there was an error generating file * * @return string The binary audio contents */ protected function audioError() { return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav'); } /** * Checks to see if headers can be sent and if any error has been output * to the browser * * @return bool true if it is safe to send headers, false if not */ protected function canSendHeaders() { if (headers_sent()) { // output has been flushed and headers have already been sent return false; } else if (strlen((string)ob_get_contents()) > 0) { // headers haven't been sent, but there is data in the buffer that will break image and audio data return false; } return true; } /** * Return a random float between 0 and 0.9999 * * @return float Random float between 0 and 0.9999 */ function frand() { return 0.0001 * mt_rand(0,9999); } /** * Convert an html color code to a Securimage_Color * @param string $color * @param Securimage_Color $default The defalt color to use if $color is invalid */ protected function initColor($color, $default) { if ($color == null) { return new Securimage_Color($default); } else if (is_string($color)) { try { return new Securimage_Color($color); } catch(Exception $e) { return new Securimage_Color($default); } } else if (is_array($color) && sizeof($color) == 3) { return new Securimage_Color($color[0], $color[1], $color[2]); } else { return new Securimage_Color($default); } } /** * The error handling function used when outputting captcha image or audio. * * This error handler helps determine if any errors raised would * prevent captcha image or audio from displaying. If they have * no effect on the output buffer or headers, true is returned so * the script can continue processing. * * See https://github.com/dapphp/securimage/issues/15 * * @param int $errno PHP error number * @param string $errstr String description of the error * @param string $errfile File error occurred in * @param int $errline Line the error occurred on in file * @param array $errcontext Additional context information * @return boolean true if the error was handled, false if PHP should handle the error */ public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) { // get the current error reporting level $level = error_reporting(); // if error was supressed or $errno not set in current error level if ($level == 0 || ($level & $errno) == 0) { return true; } return false; } } /** * Color object for Securimage CAPTCHA * * @version 3.0 * @since 2.0 * @package Securimage * @subpackage classes * */ class Securimage_Color { /** * Red value (0-255) * @var int */ public $r; /** * Gree value (0-255) * @var int */ public $g; /** * Blue value (0-255) * @var int */ public $b; /** * Create a new Securimage_Color object. * * Constructor expects 1 or 3 arguments. * * When passing a single argument, specify the color using HTML hex format. * * When passing 3 arguments, specify each RGB component (from 0-255) * individually. * * Examples: * * $color = new Securimage_Color('#0080FF'); * $color = new Securimage_Color(0, 128, 255); * * @param string $color The html color code to use * @throws Exception If any color value is not valid */ public function __construct($color = '#ffffff') { $args = func_get_args(); if (sizeof($args) == 0) { $this->r = 255; $this->g = 255; $this->b = 255; } else if (sizeof($args) == 1) { // set based on html code if (substr($color, 0, 1) == '#') { $color = substr($color, 1); } if (strlen($color) != 3 && strlen($color) != 6) { throw new InvalidArgumentException( 'Invalid HTML color code passed to Securimage_Color' ); } $this->constructHTML($color); } else if (sizeof($args) == 3) { $this->constructRGB($args[0], $args[1], $args[2]); } else { throw new InvalidArgumentException( 'Securimage_Color constructor expects 0, 1 or 3 arguments; ' . sizeof($args) . ' given' ); } } /** * Construct from an rgb triplet * * @param int $red The red component, 0-255 * @param int $green The green component, 0-255 * @param int $blue The blue component, 0-255 */ protected function constructRGB($red, $green, $blue) { if ($red < 0) $red = 0; if ($red > 255) $red = 255; if ($green < 0) $green = 0; if ($green > 255) $green = 255; if ($blue < 0) $blue = 0; if ($blue > 255) $blue = 255; $this->r = $red; $this->g = $green; $this->b = $blue; } /** * Construct from an html hex color code * * @param string $color */ protected function constructHTML($color) { if (strlen($color) == 3) { $red = str_repeat(substr($color, 0, 1), 2); $green = str_repeat(substr($color, 1, 1), 2); $blue = str_repeat(substr($color, 2, 1), 2); } else { $red = substr($color, 0, 2); $green = substr($color, 2, 2); $blue = substr($color, 4, 2); } $this->r = hexdec($red); $this->g = hexdec($green); $this->b = hexdec($blue); } }