* @license PHP License * @package WB * @subpackage base */ WBClass::load('WBService'); /** * Captcha * * Display captcha challenge and keep answer in session * * @version 0.3.2 * @package WB * @subpackage base */ class WBService_Captcha extends WBService { /** * decide whether to start locale tools * @var bool */ protected $useI18n = false; /** * decide whether to start WBConfig * @var bool */ protected $useConfig = false; /** * amount of random chars * @var int */ protected $length = 5; /** * font dir folder * @var unknown_type */ protected $fontDir = 'resource/font'; /** * current font to use * @var string */ protected $font = 'dustismo.ttf'; /** * font size * @var int */ protected $fontSize = 25; /** * text spacing factor realitve to font-size * @var float */ protected $fontSpace = 0.8; /** * maximum rotation angle of chars * @var int */ protected $fontAngle = 30; /** * amount of disturbing lines * @var int */ protected $lines = 0; /** * amount of disturbing ghost strings * @var int */ protected $ghosts = 5; /** * Use Alpha Channel * @var int 0 or 1 */ protected $bgAlpha = 0; /** * current image * @var resource */ protected $img = null; /** * list of RGB color codes * * RGB colors for foreground, background, lines and ghosts * * @var array */ protected $colorCodes = array( 'fg' => array(0, 0, 0), 'bg' => array(0xFF, 0xFF, 0xFF), 'line' => array(0x99, 0x99, 0x99), 'ghost' => array(0x77, 0x77, 0x77) ); /** * color: foreground * @var resource */ protected $colorFg; /** * color: bacground * @var resource */ protected $colorBg; /** * color: lines * @var resource */ protected $colorLine; /** * color: ghosts * @var resource */ protected $colorGhost; /** * Constructor * * Call parent constructor and load config */ public function __construct($params) { parent::__construct($params); $config = WBClass::create('WBConfig'); if (!$config->load('captcha', true)) { return; } $length = intval($config->get('image/length', $this->length)); if ($length > 2) { $this->length = $length; } // use alpha channel $this->bgAlpha = intval($config->get('image/color/alpha', 0)); // set color $fg = $config->get('image/color/foreground'); if (!empty($fg)) { $this->saveColorCode('fg', $fg); } $bg = $config->get('image/color/background'); if (!empty($bg)) { $this->saveColorCode('bg', $bg); } // set font $f = $config->get('image/font'); if (isset($f['name']) && !empty($f['name'])) { $this->setFont($f['name']); } if (isset($f['size']) && 0 < $f['size']) { $this->fontSize = max(14, intval($f['size'])); } if (isset($f['spacing']) && 0 < $f['spacing']) { $this->fontSpace = max(0.5, floatval($f['spacing'])); } if (isset($f['angle']) && -1 < $f['angle']) { $this->fontAngle = $f['angle']; } // obstacles // lines $o = $config->get('image/obstacles/lines'); if (isset($o['number'])) { $this->lines = intval($o['number']); } if (isset($o['color']) && !empty($o['color'])) { $this->saveColorCode('line', $o['color']); } // ghosts $o = $config->get('image/obstacles/ghosts'); if (isset($o['number'])) { $this->ghosts = intval($o['number']); } if (isset($o['color']) && !empty($o['color'])) { $this->saveColorCode('ghost', $o['color']); } } /** * Display/Play Captcha * * @param int $status * @param mixed $data to be send * @param string $checksum string, if any * @return bool true on success */ public function run(&$status, &$data, &$checksum = null) { // make sure session is active if (patSession::STATE_ACTIVE != $this->sess->state) { $this->sess->restart(); } $chars = $this->getRandChars($this->length); $this->sess->set('wb.service.captcha.txt', implode('', $chars)); $this->paintCaptchaImage($chars); $this->res->addHeader('Content-type', 'image/png'); ob_start(); imagepng($this->img); $data = ob_get_contents(); $checksum = md5($data); ob_end_clean(); imagedestroy($this->img); // avoid browser caches WBParam::set('wb/cache/use', 0); return true; } /** * set main colors * * Set foreground and background color * * @param string $fg * @param string $bg */ public function setColor($fg = '#000000', $bg = '#ffffff') { $this->saveColorCode('fg', $fg); $this->saveColorCode('bg', $bg); } /** * Configure font * * Set one, more or all of font name, font size, font spacing and maximum letter angle * * @param string $name * @param int $size * @param int $spacing * @param int $angle */ public function setFont($name = null, $size = null, $spacing = null, $angle = null) { if (!is_null($size)) { $this->fontSize = $size; } if (!is_null($spacing)) { $this->fontSpace = $spacing; } if (!is_null($angle)) { $this->fontAngle = $angle; } if (is_null($name)) { return; } if ($name != '__random') { $this->font = $name; return; } $fonts = array(); foreach (new DirectoryIterator(WBParam::get('wb/dir/system') . '/' . $this->fontDir) as $file) { if ($file->isDot()) { continue; } $ext = array_pop( explode('.', $file->getFilename())); if (strtolower($ext) != 'ttf') { continue; } array_push($fonts, $file->getFilename()); } $this->font = $fonts[rand(0, count($fonts) - 1)]; } /** * set number of disturbing lines * * @param int $lines * @param string $color code */ public function setLines($lines = 15, $color = '#999999') { $this->lines = $lines; $this->saveColorCode('line', $color); } /** * set number of disturbing ghost images * @param int $ghosts * @param string $color */ public function setGhosts($ghosts = 5, $color = '#999999') { $this->ghosts = $ghosts; $this->saveColorCode('ghost', $color); } /** * Create list of random characters * * Use alphabet 2-9 and a-z and select some letters * * @param int $count * @return array */ public function getRandChars($count = 5) { $chars = array_merge(range(2, 9), range('a', 'z')); $charMax = count($chars) - 1; $word = array(); for ($i = 0; $i < $count; ++$i) { $word[] = $chars[ rand(0, $charMax) ]; } return $word; } /** * Paint a picture with random text * * @param int $length how many letters should be painted * @param int $lines number of distrubing lines to draw * @return bool true on success */ public function paintCaptchaImage($chars, $lines = 15) { $width = ((count($chars) + 1) * $this->fontSize * $this->fontSpace); $height = $this->fontSize * 1.8; $this->drawImage($width, $height); $this->drawLines($width, $height); $this->drawGhosts($chars, $width, $height); $this->drawLetters($chars, $width, $height); return true; } /** * store color values * * Store color settings internally. Accept RGB color codes in * hex with prefixed '#' symbol * * @param string $name * @param string $code */ protected function saveColorCode($name, $code) { if (strlen($code) != 7) { WBClass::load('WBException_Argument'); throw new WBException_Argument('Color code must start with # followed by 6 hex digits.', 1, __CLASS__); return; } if ($code[0] != '#') { WBClass::load('WBException_Argument'); throw new WBException_Argument('Color code must start with #', 2, __CLASS__); return; } $code = substr($code, 1); $code = str_split($code, 2); foreach ($code as $i => $c) { $code[$i] = hexdec($c); } $this->colorCodes[$name] = $code; } /** * create basic canvas * * init true color image and init colors * * @param int $width * @param int $height */ protected function drawImage($width, $height) { $this->img = imagecreatetruecolor($width, $height); $this->colorFg = imagecolorallocate($this->img, $this->colorCodes['fg'][0], $this->colorCodes['fg'][1], $this->colorCodes['fg'][2]); $this->colorBg = imagecolorallocate($this->img, $this->colorCodes['bg'][0], $this->colorCodes['bg'][1], $this->colorCodes['bg'][2]); $this->colorLine = imagecolorallocate($this->img, $this->colorCodes['line'][0], $this->colorCodes['line'][1], $this->colorCodes['line'][2]); $this->colorGhost = imagecolorallocate($this->img, $this->colorCodes['ghost'][0], $this->colorCodes['ghost'][1], $this->colorCodes['ghost'][2]); if ($this->bgAlpha) { $bg = imagecolorallocatealpha($this->img, $this->colorCodes['bg'][0], $this->colorCodes['bg'][1], $this->colorCodes['bg'][2], 127); imagesavealpha($this->img, true); imagealphablending($this->img, false); imagefill($this->img, 0, 0, $bg); } else { imagefill($this->img, 0, 0, $this->colorBg); } } /** * Draw disturbing lines * * Use line color and draw number of lines on canvas * * @param int $width * @param int $height */ protected function drawLines($width, $height) { if (!$this->lines) { return; } for ($i = 0; $i < $this->lines; ++$i) { $x1 = rand(0, $width * 0.75); $y1 = rand(0, $height); $x2 = rand($width * 0.25, $width); $y2 = rand(0, $height); imageline($this->img, $x1, $y1, $x2, $y2, $this->colorLine); } } /** * Draw disturbing ghost test * * * @param array $chars * @param int $width * @param int $height */ protected function drawGhosts($chars, $width, $height) { if (!$this->ghosts) { return; } $ttf = WBParam::get('wb/dir/system') . '/' . $this->fontDir . '/' . $this->font; $string = implode('', $chars); $ang = $this->fontAngle / 3; for ($i = 0; $i < $this->ghosts; ++$i) { $a = rand(-$ang, $ang); $x = rand(0, $this->fontSize); $y = rand(0, $this->fontSize * 2); imagettftext($this->img, $this->fontSize, $a, $x, $y, $this->colorGhost, $ttf, $string); } } /** * Draw actual payload * * * * @param array $chars * @param int $width * @param int $height */ protected function drawLetters($chars, $width, $height) { $ttf = WBParam::get('wb/dir/system') . '/' . $this->fontDir . '/' . $this->font; $x = $this->fontSize / 4; $y = $this->fontSize * 1.25; foreach ($chars as $char) { $a = rand(-$this->fontAngle, $this->fontAngle); imagettftext($this->img, $this->fontSize, $a, $x, $y, $this->colorFg, $ttf, $char); $x += $this->fontSize * $this->fontSpace; } } }