Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  
   3   /**
   4    * CFTypeDetector
   5    * Interface for converting native PHP data structures to CFPropertyList objects.
   6    * @author Rodney Rehm <rodney.rehm@medialize.de>
   7    * @author Christian Kruse <cjk@wwwtech.de>
   8    * @package plist
   9    * @subpackage plist.types
  10    * @example example-create-02.php Using {@link CFTypeDetector}
  11    * @example example-create-03.php Using {@link CFTypeDetector} with {@link CFDate} and {@link CFData}
  12    * @example example-create-04.php Using and extended {@link CFTypeDetector}
  13    */
  14  
  15  namespace CFPropertyList;
  16  use \DateTime, \Iterator;
  17  
  18  class CFTypeDetector {
  19  
  20    /**
  21     * flag stating if all arrays should automatically be converted to {@link CFDictionary}
  22     * @var boolean
  23     */
  24    protected $autoDictionary = false;
  25  
  26    /**
  27     * flag stating if exceptions should be suppressed or thrown
  28     * @var boolean
  29     */
  30    protected $suppressExceptions = false;
  31  
  32    /**
  33     * name of a method that will be used for array to object conversations
  34     * @var callable
  35     */
  36    protected $objectToArrayMethod = null;
  37  
  38    /**
  39     * flag stating if "123.23" should be converted to float (true) or preserved as string (false)
  40     * @var boolean
  41     */
  42    protected $castNumericStrings = true;
  43  
  44  
  45    /**
  46     * Create new CFTypeDetector
  47     * @param array $options Configuration for casting values [autoDictionary, suppressExceptions, objectToArrayMethod, castNumericStrings]
  48     */
  49    public function __construct(array $options=array()) {
  50      //$autoDicitionary=false,$suppressExceptions=false,$objectToArrayMethod=null
  51      foreach ($options as $key => $value) {
  52        if (property_exists($this, $key)) {
  53          $this->$key = $value;
  54        }
  55      }
  56    }
  57  
  58    /**
  59     * Determine if an array is associative or numerical.
  60     * Numerical Arrays have incrementing index-numbers that don't contain gaps.
  61     * @param array $value Array to check indexes of
  62     * @return boolean true if array is associative, false if array has numeric indexes
  63     */
  64    protected function isAssociativeArray($value) {
  65      $numericKeys = true;
  66      $i = 0;
  67      foreach($value as $key => $v) {
  68        if($i !== $key) {
  69          $numericKeys = false;
  70          break;
  71        }
  72        $i++;
  73      }
  74      return !$numericKeys;
  75    }
  76  
  77    /**
  78     * Get the default value
  79     * @return CFType the default value to return if no suitable type could be determined
  80     */
  81    protected function defaultValue() {
  82      return new CFString();
  83    }
  84  
  85    /**
  86     * Create CFType-structure by guessing the data-types.
  87     * {@link CFArray}, {@link CFDictionary}, {@link CFBoolean}, {@link CFNumber} and {@link CFString} can be created, {@link CFDate} and {@link CFData} cannot.
  88     * <br /><b>Note:</b>Distinguishing between {@link CFArray} and {@link CFDictionary} is done by examining the keys.
  89     * Keys must be strictly incrementing integers to evaluate to a {@link CFArray}.
  90     * Since PHP does not offer a function to test for associative arrays,
  91     * this test causes the input array to be walked twice and thus work rather slow on large collections.
  92     * If you work with large arrays and can live with all arrays evaluating to {@link CFDictionary},
  93     * feel free to set the appropriate flag.
  94     * <br /><b>Note:</b> If $value is an instance of CFType it is simply returned.
  95     * <br /><b>Note:</b> If $value is neither a CFType, array, numeric, boolean nor string, it is omitted.
  96     * @param mixed $value Value to convert to CFType
  97     * @param boolean $autoDictionary if true {@link CFArray}-detection is bypassed and arrays will be returned as {@link CFDictionary}.
  98     * @return CFType CFType based on guessed type
  99     * @uses isAssociativeArray() to check if an array only has numeric indexes
 100     */
 101    public function toCFType($value) {
 102      switch(true) {
 103        case $value instanceof CFType:
 104          return $value;
 105        break;
 106  
 107        case is_object($value):
 108          // DateTime should be CFDate
 109          if(class_exists( 'DateTime' ) && $value instanceof DateTime){
 110            return new CFDate($value->getTimestamp());
 111          }
 112  
 113          // convert possible objects to arrays, arrays will be arrays
 114          if($this->objectToArrayMethod && is_callable(array($value, $this->objectToArrayMethod))){
 115            $value = call_user_func( array( $value, $this->objectToArrayMethod ) );
 116          }
 117  
 118          if(!is_array($value)){
 119            if($this->suppressExceptions)
 120              return $this->defaultValue();
 121  
 122            throw new PListException('Could not determine CFType for object of type '. get_class($value));
 123          }
 124        /* break; omitted */
 125  
 126        case $value instanceof Iterator:
 127        case is_array($value):
 128          // test if $value is simple or associative array
 129          if(!$this->autoDictionary) {
 130            if(!$this->isAssociativeArray($value)) {
 131              $t = new CFArray();
 132              foreach($value as $v) $t->add($this->toCFType($v));
 133              return $t;
 134            }
 135          }
 136  
 137          $t = new CFDictionary();
 138          foreach($value as $k => $v) $t->add($k, $this->toCFType($v));
 139  
 140          return $t;
 141        break;
 142  
 143        case is_bool($value):
 144          return new CFBoolean($value);
 145        break;
 146  
 147        case is_null($value):
 148          return new CFString();
 149        break;
 150  
 151        case is_resource($value):
 152          if ($this->suppressExceptions) {
 153            return $this->defaultValue();
 154          }
 155  
 156          throw new PListException('Could not determine CFType for resource of type '. get_resource_type($value));
 157        break;
 158  
 159        case is_numeric($value):
 160          if (!$this->castNumericStrings && is_string($value)) {
 161            return new CFString($value);
 162          }
 163  
 164          return new CFNumber($value);
 165        break;
 166  
 167        case is_string($value):
 168          if(strpos($value, "\x00") !== false) {
 169            return new CFData($value);
 170          }
 171          return new CFString($value);
 172  
 173        break;
 174  
 175        default:
 176          if ($this->suppressExceptions) {
 177            return $this->defaultValue();
 178          }
 179  
 180          throw new PListException('Could not determine CFType for '. gettype($value));
 181        break;
 182      }
 183    }
 184  
 185  }