<?php

/**
 * validate
 *
 * @package mtc ecommerce
 * @author mtc.
 * @copyright 2013 mtc. http://www.mtcmedia.co.uk/
 * @version 2013
 * @access public
 */
class validate
{
    /**
     * A basic Phone regular expression. ensures that string consists only of + and numbers dash(-) or space
     */
    const PHONE_REGEX = '/^(\+)?([0-9- ])+$/';
//******************************************************************************
//*************************SET UP ALL ARRAY VARIABLES***************************
//******************************************************************************

  protected $_inputType; //stores the inputType (get or post)
  protected $_submitted; //this is an array that holds all values of unfiltered input
  protected $_required; //this holds all required field names
  protected $_filterArgs; //all filter arguements flags and options etc
  protected $_filtered; //holds the list of all the fields which have been filtered
  protected $_missing; //is the comparison of filtered and required to check if any are missing
  protected $_errors; //holds all errors within an array
  protected $_booleans; //holds all the boolean field names passed through

//******************************************************************************
//**************************SET UP CLASS CONSTRUCTOR****************************
//******************************************************************************
/**
 * validate::__construct()
 *
 * @return
 */
public function __construct($required = array(), $inputType = 'post') //required is a new array, input type is set at a default of post unless otherwise stated!
{

      if (!is_null($required) && !is_array($required)) {
        throw new Exception('The names of required fields must be an array, even if only one field is required.');
      }//throw exception when $required is null or if $required is not passed as an array, all field values must be passed in an array

      $this -> _required = $required; //set  _required to equal the $required array making this protected within the class
      $this -> setInputType($inputType);

      if ($this->_required) {
        $this -> checkRequired(); //passes _required to checkRequired to assertain whether all fields within array have a value
      }

      $this -> _filterArgs = array();
      $this -> _errors = array(); //give errors an empty array to populate with general errors not relating to filter arguements
      $this->_booleans = array(); //sets the booleans array
}

//******************************************************************************
//**************************SET INPUT TYPE GET OR POST**************************
//******************************************************************************
/**
 * validate::setInputType()
 *
 * @return
 */
protected function setInputType($type)
{
  switch (strtolower($type)) { //switches out _GET or _POST superglobals for INPUT_ filters for later
    case 'get':
      $this-> _inputType = INPUT_GET;
      $this-> _submitted = $_GET;
      break;

    case 'post':
      $this-> _inputType = INPUT_POST;
      $this-> _submitted = $_POST;
      break;

    default:
      throw new exception ('Invalid input type, only post or get are allowed!');
  }

}

//******************************************************************************
//************************ENSURE ALL REQUIRED FIELDS ARE COMPLETE***************
//******************************************************************************
/**
 * validate::checkRequired()
 *
 * @return
 */
protected function checkRequired()
{
  $passed = array();

  foreach ($this->_submitted as $name => $field) { //pass in the array of submitted
    $field = is_array($field) ? $field : trim($field); //passes in each value and trims it so spacebar chars dont fool it
    if (!empty($field)) {
      $passed[] = $name;
    }
  }

  $this->_missing = array_diff($this->_required, $passed);
}

//******************************************************************************
//************************ENSURE VALIDATION IS NOT DUPLICATED*******************
//******************************************************************************
/**
 * validate::checkFilters()
 *
 * @return
 */
protected function checkFilters($fieldName) { //ensures only one set of filters are applied to a field value!
  //this method is called in ech validation operation to ensure no duplications
  if (isset($this-> _filterArgs[$fieldName])) {
    throw new Exception ('Filters have already been applied to '. $fieldName);
  }
}

//******************************************************************************
//*****************DEALS WITH ALL VALIDATION OF ARRAY DATA**********************
//******************************************************************************

/**
 * validate::validateAllInput()
 *
 * @return
 */
public function validateAllInput()
{
  //make an array for items not filtered
  $notFiltered = array();
  // create a list of tested fields
  $validFields = array_keys($this->_filterArgs);

  foreach ($this->_required as $field) {
    if (!in_array($field, $validFields)) { //checks if $field exists within $validFields array
      $notFiltered[] = $field; //if not add the field to notFiltered
    }
  }

  if ($notFiltered) { //if values exist within notFiltered follow up and throw exception
    throw new exception ('No filter has been applied to the following required field: '. implode(',', $notFiltered));
  }

  // filter_input_array('input type', 'filter flags and options array')
  $this->_filtered = filter_input_array($this->_inputType, $this->_filterArgs); //this is where all fields are validated

  //check which fields have validated
  foreach ($this->_filtered as $key => $value) { //$key will be name of field
    //skip possible boolean values as these will flag false as a value
    //also skip any that are missing or not required, compare _filtered to _required

    if (in_array($key, $this->_booleans)|| in_array($key, $this->_missing) || !in_array($key, $this->_required)) {
      continue; //allows continuation of script no exception
    } elseif ($value === false) {
      $this->_errors[$key] = ucwords(str_replace('_', ' ', $key)) . ': invalid data supplied'; //populates errors array with invalid field names
    }

  }

  //return the validated input as an array
  return $this->_filtered; //returns filtered for use in outside functions

}

//******************************************************************************
//****************RETURNS ALL MAIN ERRORS AND FILTERED ARRAYS********************
//******************************************************************************
// return all protected arrays for missing, errors and filtered arrays
/**
 * validate::getMissing()
 *
 * @return
 */
public function getMissing()
{
  return $this->_missing;
}
/**
 * validate::getErrors()
 *
 * @return
 */
public function getErrors()
{
  return $this->_errors;
}
/**
 * validate::getFiltered()
 *
 * @return
 */
public function getFiltered()
{
  return $this->_filtered;
}
/**
 * validate::getSubmitted()
 *
 * @return
 */
public function getSubmitted()
{
  return array_keys($this->_submitted);
}

//******************************************************************************
//************************ALL VALIDATION OF FIELDS IS PLACED BELOW**************
//******************************************************************************

/*
*
* @param string  $fieldName     Name of field of submitted value to be checked
* @param int     $minVal        Optional; Allows a minimum value to be set; defaults to null
* @param int     $maxVal        Optional; Allows a maximum Value to be set; defaults to null
*
*/
//validating integers
/**
 * validate::isInt()
 *
 * @return
 */
public function isInt($fieldName, $minVal= null, $maxVal = null)
{
  $this->checkFilters($fieldName); //must always be called first so as to allow for a duplicate check

  $this-> _filterArgs[$fieldName] = array('filter' => FILTER_VALIDATE_INT); //assigns the array field 'filter' with the appropriate filter constant

  if (is_int($minVal)) { //if minVal is an int
    $this-> _filterArgs[$fieldName]['options']['min_range'] = $minVal; //assign the minVal to the options of the field
  }
  if (is_int($maxVal)) { //if maxVal is an int
    $this-> _filterArgs[$fieldName]['options']['max_range'] = $maxVal;
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
*
* @param string  $fieldName     Name of field of submitted value to be checked
* @param string  $decimalPoint  Optional; Allows a decimal point character to be set ; defaults to period;
*
*/

//validating floats
/**
 * validate::isFloat()
 *
 * @return
 */
public function isFloat($fieldName, $decimalPoint = '.')
{
  $this->checkFilters($fieldName); //must always be called first so as to allow for a duplicate check

  $this-> _filterArgs[$fieldName] = array(
    'filter' => FILTER_VALIDATE_FLOAT,
    'options' => array('decimal' => $decimalPoint)
  );
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//validating booleans
/**
 * validate::isBool()
 *
 * @return
 */
public function isBool($fieldName, $nullOnFailure = false)
{
  $this->checkFilters($fieldName);
  $this->_booleans[] = $fieldName;
  $this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_BOOLEAN;

  if ($nullOnFailure) {
    $this->_filterArgs[$fieldName]['flags'] = FILTER_NULL_ON_FAILURE;
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
*******************THIS FUNCTION CAN BE USED IN CONJUNCTION WITH OTHER FUNCTIONS********************
*
* @param string  $fieldName     Name of field of submitted value to be checked
* @param int     $min           Requires (can be set to 0); Allows a minimum character limit to be set; defaults to null;
* @param int     $max           Optional; Allows a maximum character limit to be set; defaults to null;
*
*/

//validates lenght of text and can be called in conjunction with other functions
/**
 * validate::checkTextLength()
 *
 * @return
 */
public function checkTextLength($fieldName, $min, $max = null)
{
      $submittedValue = trim($this->_submitted[$fieldName]);

      if (!is_string($submittedValue)) {
        throw new exception('checkTextLength is for strings only '.$fieldName.' is not a string');
      }

      if (!is_numeric($min)) {
        throw new exception('checkTextLength $min must be a numeric value, ('.$fieldName.')');
      }

      if ($submittedValue < $min) {
        if (is_numeric($max)) {
          if ($min==$max) {
            $this->_errors[$fieldName] = $fieldName.' must be '.$min.' characters';
          } else {
            $this->_errors[$fieldName] = $fieldName.' must be between '.$min.' and '.$max.' characters';
          }
        } else {
          $this->_errors[$fieldName] = $fieldName.' must be at least '.$min.' characters';
        }
      }

      if (is_numeric($max) && strlen($submittedValue)>$max) {
        if ($min==0) {
          $this->_errors[$fieldName] = $fieldName.' can only contain a maximum of '.$max.' characters, you currently have - '.strlen($submittedValue).' characters';
        } else {
          if ($min==$max) {
              $this->_errors[$fieldName] = $fieldName.' must be '.$min.' characters, you currently have - '.strlen($submittedValue).' characters';
            } else {
              $this->_errors[$fieldName] = $fieldName.' must be between '.$min.' and '.$max.' characters, you currently have - '.strlen($submittedValue).' characters';
            }
        }

      }

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Sanitizes array by removing completely all tags (including PHP and HTML).
/**
 * validate::removeTags()
 *
 * @return
 */
public function removeTags($fieldName, $encodeAmp = false, $preserveQuotes = false, $encodeLow = false, $encodeHigh = false, $stripLow = false, $stripHigh = false)
{
    $this->checkFilters($fieldName);

    $this->_filterArgs[$fieldName]['filter'] = FILTER_SANITIZE_STRING;

    $this->_filterArgs[$fieldName]['flags'] = 0;
    if ($encodeAmp) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_AMP;
    }
    if ($preserveQuotes) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_NO_ENCODE_QUOTES;
    }
    if ($encodeLow) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_LOW;
    }
    if ($encodeHigh) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_HIGH;
    }
    if ($stripLow) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_STRIP_LOW;
    }
    if ($stripHigh) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_STRIP_HIGH;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Sanitizes array by removing completely all tags (including PHP and HTML).
/**
 * validate::removeTagsFromArray()
 *
 * @return
 */
public function removeTagsFromArray($fieldName, $encodeAmp = false, $preserveQuotes = false, $encodeLow = false, $encodeHigh = false, $stripLow = false, $stripHigh = false)
{
    $this->checkFilters($fieldName);

    $this->_filterArgs[$fieldName]['filter'] = FILTER_SANITIZE_STRING;

    $this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
    if ($encodeAmp) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_AMP;
    }
    if ($preserveQuotes) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_NO_ENCODE_QUOTES;
    }
    if ($encodeLow) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_LOW;
    }
    if ($encodeHigh) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_HIGH;
    }
    if ($stripLow) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_STRIP_LOW;
    }
    if ($stripHigh) {
        $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_STRIP_HIGH;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * validate::specialChars()
 *
 * @return
 */
public function specialChars($fieldName, $stripLow = false, $stripHigh = false, $encodeHigh = false)
{
  //sanitizes special chars from an input called from
  $this->checkFilters($fieldName);
  $this->_filterArgs[$fieldName]['filter'] = FILTER_SANITIZE_SPECIAL_CHARS;
  $this->_filterArgs[$fieldName]['flags'] = 0;

  if ($isArray) {
    $this->_filterArgs[$fieldName]['flags'] |= FILTER_REQUIRE_ARRAY;
  }

  if ($encodeHigh) {
    $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_HIGH;
  }

  if ($stripLow) {
    $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_STRIP_LOW;
  }

  if ($stripHigh) {
    $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_STRIP_HIGH;
  }

}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  //validate emails
/**
 * validate::isEmail()
 *
 * @return
 */
public function isEmail($fieldName)
{
  $this->checkFilters($fieldName);
  $this->_filterArgs[$fieldName] = FILTER_VALIDATE_EMAIL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//validate against any regexp or use predefined cases
/**
 * validate::isRegexp()
 *
 * @return
 */
public function isRegexp($fieldName, $regExp = null, $identity = null)
{
  $this->checkFilters($fieldName);

  if (!$regExp && !$identity) {
    throw new exception('You must enter a Regular expression or an identity for '.$fieldName);
  }

  $this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_REGEXP;

  if ($regExp) {
      $this->_filterArgs[$fieldName]['options']['regexp'] = $regExp;
  } else {
    switch ($identity) {

    //when adding a new regexp ensure it is perl ready!

      case 'name':
        //allows alphabetic characters only
        //NEEDS EXPANSION FOR '- [SPACE] AND POSSIBLY A LENGTH RESTRICTION
        $regExp = '/^[a-zA-Z\']*$/';
        break;

      case 'passWeak':
        //weak validation (must be at least 5 chars)
        $regExp = '/^([A-Za-z0-9].{5,15})$/';
        break;

      case 'passMedium':
        //medium validation (must contain a number and lowercase letters min 5chars max 15)
        $regExp = '/^((?=.*\d)(?=.*[a-z]).{5,15})$/';
        break;

      case 'passStrong':
        //strong validation (must contain a capital, a number and a lowercase letter min 8chars, max 15 )
        $regExp = '/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,15})$/';
        break;

      case 'telNum':
        //validate a telephone number
        //needs refinement!
       $regExp = self::PHONE_REGEX;
       break;

      case 'currency':
        $regExp = '/^([�$]?[0-9]{0,3}[,]?[0-9]{0,3}[,]?[0-9]{0,3}[.]{1}[0-9]{2})$/';
        break;

      case 'postCode':
        //validates UK postcodes
        $regExp = '/^\s*([A-Za-z]{1,2}[0-9]{1,2}[a-zA-z]?)(\s*)([0-9]{1}[A-Za-z]{2})\s*$/';
        break;

      case 'realDate':
        //works for DD MM YYYY only
        $regExp = '/^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?[0-9]{2})*$/';
        break;

      default:
        throw new exception($identity.' is not a valid identity variable. Please add a new Regular expression or choose from the above!');

    }

    $this->_filterArgs[$fieldName]['options']['regexp'] = $regExp;
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//validate a URL
/**
 * validate::isURL()
 *
 * @return
 */
public function isURL($fieldName, $fullUrl = false, $queryStringRequired = false)
{
  $this->checkFilters($fieldName);

  $this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_URL;

  switch ($fullUrl) {
    case true:
      $this->_filterArgs[$fieldName]['flags'] = FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED;
      break;
    case false:
      $this->_filterArgs[$fieldName]['flags'] = 0;
      break;
    default:
      throw new exception('A URL must have a $fullURL variable of True or False!');
  }

  if ($queryStringRequired) {
    if ($fullUrl) {
      $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_QUERY_REQUIRED;
    }
  }

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//validates that two strings match
/**
 * validate::isMatch()
 *
 * @return
 */
public function isMatch($fieldName1=null, $fieldName2=null)
{
  //dont check for filters applied as this allows for password checks
  //instead get the actual submitted values
   $field1 = trim($this->_submitted[$fieldName1]);
   $field2 = trim($this->_submitted[$fieldName2]);

  if (($field1!='') && ($field2!='')) {
    if ($field1!=$field2) {
      $fieldName_1 = ucwords(str_replace('_', ' ', $fieldName1));
      $fieldName_2 = ucwords(str_replace('_', ' ', $fieldName2));
      $this->_errors[$fieldName2] = 'The fields '.$fieldName_1.' & '.$fieldName_2.' must match';
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * validate::noFilter()
 *
 * @return
 */
public function noFilter($fieldName, $isArray = false, $encodeAmp = false)
{
  $this->checkFilters($fieldName);
  $this->_filterArgs[$fieldName]['filter'] = FILTER_UNSAFE_RAW;
  $this->_filterArgs[$fieldName]['flags'] = 0;
  if ($isArray) {
    $this->_filterArgs[$fieldName]['flags'] |= FILTER_REQUIRE_ARRAY;
  }
  if ($encodeAmp) {
    $this->_filterArgs[$fieldName]['flags'] |= FILTER_FLAG_ENCODE_AMP;
  }
}

/****************************************************************************************************
                        This must be at base before curly braces to finish class
****************************************************************************************************/

}
