1<?php 2/** 3 * Copyright 2017 Facebook, Inc. 4 * 5 * You are hereby granted a non-exclusive, worldwide, royalty-free license to 6 * use, copy, modify, and distribute this software in source code or binary 7 * form for use in connection with the web services and APIs provided by 8 * Facebook. 9 * 10 * As with any software that integrates with the Facebook platform, your use 11 * of this software is subject to the Facebook Developer Principles and 12 * Policies [http://developers.facebook.com/policy/]. This copyright notice 13 * shall be included in all copies or substantial portions of the software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 */ 24namespace Facebook\GraphNodes; 25 26/** 27 * Class GraphNode 28 * 29 * @package Facebook 30 */ 31class GraphNode extends Collection 32{ 33 /** 34 * @var array Maps object key names to Graph object types. 35 */ 36 protected static $graphObjectMap = []; 37 38 /** 39 * Init this Graph object. 40 * 41 * @param array $data 42 */ 43 public function __construct(array $data = []) 44 { 45 parent::__construct($this->castItems($data)); 46 } 47 48 /** 49 * Iterates over an array and detects the types each node 50 * should be cast to and returns all the items as an array. 51 * 52 * @TODO Add auto-casting to AccessToken entities. 53 * 54 * @param array $data The array to iterate over. 55 * 56 * @return array 57 */ 58 public function castItems(array $data) 59 { 60 $items = []; 61 62 foreach ($data as $k => $v) { 63 if ($this->shouldCastAsDateTime($k) 64 && (is_numeric($v) 65 || $this->isIso8601DateString($v)) 66 ) { 67 $items[$k] = $this->castToDateTime($v); 68 } elseif ($k === 'birthday') { 69 $items[$k] = $this->castToBirthday($v); 70 } else { 71 $items[$k] = $v; 72 } 73 } 74 75 return $items; 76 } 77 78 /** 79 * Uncasts any auto-casted datatypes. 80 * Basically the reverse of castItems(). 81 * 82 * @return array 83 */ 84 public function uncastItems() 85 { 86 $items = $this->asArray(); 87 88 return array_map(function ($v) { 89 if ($v instanceof \DateTime) { 90 return $v->format(\DateTime::ISO8601); 91 } 92 93 return $v; 94 }, $items); 95 } 96 97 /** 98 * Get the collection of items as JSON. 99 * 100 * @param int $options 101 * 102 * @return string 103 */ 104 public function asJson($options = 0) 105 { 106 return json_encode($this->uncastItems(), $options); 107 } 108 109 /** 110 * Detects an ISO 8601 formatted string. 111 * 112 * @param string $string 113 * 114 * @return boolean 115 * 116 * @see https://developers.facebook.com/docs/graph-api/using-graph-api/#readmodifiers 117 * @see http://www.cl.cam.ac.uk/~mgk25/iso-time.html 118 * @see http://en.wikipedia.org/wiki/ISO_8601 119 */ 120 public function isIso8601DateString($string) 121 { 122 // This insane regex was yoinked from here: 123 // http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ 124 // ...and I'm all like: 125 // http://thecodinglove.com/post/95378251969/when-code-works-and-i-dont-know-why 126 $crazyInsaneRegexThatSomehowDetectsIso8601 = '/^([\+-]?\d{4}(?!\d{2}\b))' 127 . '((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?' 128 . '|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d' 129 . '|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])' 130 . '((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d' 131 . '([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/'; 132 133 return preg_match($crazyInsaneRegexThatSomehowDetectsIso8601, $string) === 1; 134 } 135 136 /** 137 * Determines if a value from Graph should be cast to DateTime. 138 * 139 * @param string $key 140 * 141 * @return boolean 142 */ 143 public function shouldCastAsDateTime($key) 144 { 145 return in_array($key, [ 146 'created_time', 147 'updated_time', 148 'start_time', 149 'end_time', 150 'backdated_time', 151 'issued_at', 152 'expires_at', 153 'publish_time' 154 ], true); 155 } 156 157 /** 158 * Casts a date value from Graph to DateTime. 159 * 160 * @param int|string $value 161 * 162 * @return \DateTime 163 */ 164 public function castToDateTime($value) 165 { 166 if (is_int($value)) { 167 $dt = new \DateTime(); 168 $dt->setTimestamp($value); 169 } else { 170 $dt = new \DateTime($value); 171 } 172 173 return $dt; 174 } 175 176 /** 177 * Casts a birthday value from Graph to Birthday 178 * 179 * @param string $value 180 * 181 * @return Birthday 182 */ 183 public function castToBirthday($value) 184 { 185 return new Birthday($value); 186 } 187 188 /** 189 * Getter for $graphObjectMap. 190 * 191 * @return array 192 */ 193 public static function getObjectMap() 194 { 195 return static::$graphObjectMap; 196 } 197} 198