<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Captcha;

use 
Zend\Session\Container;
use 
Zend\Math\Rand;

/**
 * AbstractWord-based captcha adapter
 *
 * Generates random word which user should recognise
 */
abstract class AbstractWord extends AbstractAdapter
{
    
/**#@+
     * @var array Character sets
     */
    
public static $V  = array("a""e""i""o""u""y");
    public static 
$VN = array("a""e""i""o""u""y","2","3","4","5","6","7","8","9");
    public static 
$C  = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
    public static 
$CN = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z","2","3","4","5","6","7","8","9");
    
/**#@-*/

    /**
     * Random session ID
     *
     * @var string
     */
    
protected $id;

    
/**
     * Generated word
     *
     * @var string
     */
    
protected $word;

    
/**
     * Session
     *
     * @var Container
     */
    
protected $session;

    
/**
     * Class name for sessions
     *
     * @var string
     */
    
protected $sessionClass 'Zend\Session\Container';

    
/**
     * Should the numbers be used or only letters
     *
     * @var bool
     */
    
protected $useNumbers true;

    
/**
     * Should both cases be used or only lowercase
     *
     * @var bool
     */
    // protected $useCase = false;

    /**
     * Session lifetime for the captcha data
     *
     * @var integer
     */
    
protected $timeout 300;

    
/**
     * Should generate() keep session or create a new one?
     *
     * @var bool
     */
    
protected $keepSession false;

    
/**#@+
     * Error codes
     */
    
const MISSING_VALUE 'missingValue';
    const 
MISSING_ID    'missingID';
    const 
BAD_CAPTCHA   'badCaptcha';
    
/**#@-*/

    /**
     * Error messages
     * @var array
     */
    
protected $messageTemplates = array(
        
self::MISSING_VALUE => 'Empty captcha value',
        
self::MISSING_ID    => 'Captcha ID field is missing',
        
self::BAD_CAPTCHA   => 'Captcha value is wrong',
    );

    
/**
     * Length of the word to generate
     *
     * @var integer
     */
    
protected $wordlen 8;

    
/**
     * Retrieve session class to utilize
     *
     * @return string
     */
    
public function getSessionClass()
    {
        return 
$this->sessionClass;
    }

    
/**
     * Set session class for persistence
     *
     * @param  string $sessionClass
     * @return AbstractWord
     */
    
public function setSessionClass($sessionClass)
    {
        
$this->sessionClass $sessionClass;
        return 
$this;
    }

    
/**
     * Retrieve word length to use when generating captcha
     *
     * @return integer
     */
    
public function getWordlen()
    {
        return 
$this->wordlen;
    }

    
/**
     * Set word length of captcha
     *
     * @param integer $wordlen
     * @return AbstractWord
     */
    
public function setWordlen($wordlen)
    {
        
$this->wordlen $wordlen;
        return 
$this;
    }

    
/**
     * Retrieve captcha ID
     *
     * @return string
     */
    
public function getId()
    {
        if (
null === $this->id) {
            
$this->setId($this->generateRandomId());
        }
        return 
$this->id;
    }

    
/**
     * Set captcha identifier
     *
     * @param string $id
     * @return AbstractWord
     */
    
protected function setId ($id)
    {
        
$this->id $id;
        return 
$this;
    }

    
/**
     * Set timeout for session token
     *
     * @param  int $ttl
     * @return AbstractWord
     */
    
public function setTimeout($ttl)
    {
        
$this->timeout = (int) $ttl;
        return 
$this;
    }

    
/**
     * Get session token timeout
     *
     * @return int
     */
    
public function getTimeout()
    {
        return 
$this->timeout;
    }

    
/**
     * Sets if session should be preserved on generate()
     *
     * @param bool $keepSession Should session be kept on generate()?
     * @return AbstractWord
     */
    
public function setKeepSession($keepSession)
    {
        
$this->keepSession $keepSession;
        return 
$this;
    }

    
/**
     * Numbers should be included in the pattern?
     *
     * @return bool
     */
    
public function getUseNumbers()
    {
        return 
$this->useNumbers;
    }

    
/**
     * Set if numbers should be included in the pattern
     *
     * @param  bool $useNumbers numbers should be included in the pattern?
     * @return AbstractWord
     */
    
public function setUseNumbers($useNumbers)
    {
        
$this->useNumbers $useNumbers;
        return 
$this;
    }

    
/**
     * Get session object
     *
     * @throws Exception\InvalidArgumentException
     * @return Container
     */
    
public function getSession()
    {
        if (!isset(
$this->session) || (null === $this->session)) {
            
$id $this->getId();
            if (!
class_exists($this->sessionClass)) {
                throw new 
Exception\InvalidArgumentException("Session class $this->sessionClass not found");
            }
            
$this->session = new $this->sessionClass('Zend_Form_Captcha_' $id);
            
$this->session->setExpirationHops(1nulltrue);
            
$this->session->setExpirationSeconds($this->getTimeout());
        }
        return 
$this->session;
    }

    
/**
     * Set session namespace object
     *
     * @param  Container $session
     * @return AbstractWord
     */
    
public function setSession(Container $session)
    {
        
$this->session $session;
        if (
$session) {
            
$this->keepSession true;
        }
        return 
$this;
    }

    
/**
     * Get captcha word
     *
     * @return string
     */
    
public function getWord()
    {
        if (empty(
$this->word)) {
            
$session     $this->getSession();
            
$this->word  $session->word;
        }
        return 
$this->word;
    }

    
/**
     * Set captcha word
     *
     * @param  string $word
     * @return AbstractWord
     */
    
protected function setWord($word)
    {
        
$session       $this->getSession();
        
$session->word $word;
        
$this->word    $word;
        return 
$this;
    }

    
/**
     * Generate new random word
     *
     * @return string
     */
    
protected function generateWord()
    {
        
$word       '';
        
$wordLen    $this->getWordLen();
        
$vowels     $this->useNumbers ? static::$VN : static::$V;
        
$consonants $this->useNumbers ? static::$CN : static::$C;

        for (
$i=0$i $wordLen$i $i 2) {
            
// generate word with mix of vowels and consonants
            
$consonant $consonants[array_rand($consonants)];
            
$vowel     $vowels[array_rand($vowels)];
            
$word     .= $consonant $vowel;
        }

        if (
strlen($word) > $wordLen) {
            
$word substr($word0$wordLen);
        }

        return 
$word;
    }

    
/**
     * Generate new session ID and new word
     *
     * @return string session ID
     */
    
public function generate()
    {
        if (!
$this->keepSession) {
            
$this->session null;
        }
        
$id $this->generateRandomId();
        
$this->setId($id);
        
$word $this->generateWord();
        
$this->setWord($word);
        return 
$id;
    }

    
/**
     * Generate a random identifier
     *
     * @return string
     */
    
protected function generateRandomId()
    {
        return 
md5(Rand::getBytes(32));
    }

    
/**
     * Validate the word
     *
     * @see    Zend\Validator\ValidatorInterface::isValid()
     * @param  mixed $value
     * @param  mixed $context
     * @return bool
     */
    
public function isValid($value$context null)
    {
        if (!
is_array($value)) {
            if (!
is_array($context)) {
                
$this->error(self::MISSING_VALUE);
                return 
false;
            }
            
$value $context;
        }

        
$name $this->getName();

        if (isset(
$value[$name])) {
            
$value $value[$name];
        }

        if (!isset(
$value['input'])) {
            
$this->error(self::MISSING_VALUE);
            return 
false;
        }
        
$input strtolower($value['input']);
        
$this->setValue($input);

        if (!isset(
$value['id'])) {
            
$this->error(self::MISSING_ID);
            return 
false;
        }

        
$this->id $value['id'];
        if (
$input !== $this->getWord()) {
            
$this->error(self::BAD_CAPTCHA);
            return 
false;
        }

        return 
true;
    }

    
/**
     * Get helper name used to render captcha
     *
     * @return string
     */
    
public function getHelperName()
    {
        return 
'captcha/word';
    }
}