Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
data_source_json.inc
1 <?php
18 require_once SQ_INCLUDE_PATH.'/asset.inc';
19 require_once SQ_CORE_PACKAGE_PATH.'/data_source/data_source/data_source.inc';
20 require_once SQ_LIB_PATH.'/html_form/html_form.inc';
21 
34 {
35 
42  function __construct($assetid=0)
43  {
44  parent::__construct($assetid);
45 
46  }//end constructor
47 
48 
55  function lockTypes()
56  {
57  $lock_types = parent::lockTypes();
58  $lock_types['json_data_source'] = ($lock_types['attributes'] | $lock_types['links']);
59  return $lock_types;
60 
61  }//end lockTypes()
62 
63 
77  function _getAllowedLinks()
78  {
79  // any link is allowed
80  $allowed_link['asset']['card'] = 'M';
81  $allowed_link['asset']['exclusive'] = FALSE;
82 
83  $links[SQ_LINK_TYPE_1] = $allowed_link;
84  $links[SQ_LINK_TYPE_2] = $allowed_link;
85  $links[SQ_LINK_TYPE_3] = $allowed_link;
86  $links[SQ_LINK_NOTICE] = $allowed_link;
87 
88  return $links;
89 
90  }//end _getAllowedLinks()
91 
92 
102  function getItems()
103  {
104  // Get the source data
105  $json_data = $this->getRawSourceData();
106  if (empty($json_data)) {
107  return Array();
108  }
109 
110  // Run data through the parser
111  $results = $this->_parse($json_data);
112 
113  // For the top-level shadow asset attributes, if there are any arrays
114  // remaining, re-encode them to JSON so that the value of the attribute
115  // becomes the JSON data (for user visibility)
116  foreach ($results as &$asset) {
117  foreach ($asset as &$attr) {
118  if (is_array($attr)) {
119  $attr = self::_encodeJson($attr);
120  }
121  }
122  }
123 
124  return $results;
125 
126  }//end getItems()
127 
128 
137  public function getRawSourceData()
138  {
139  $source = $this->_getSource();
140 
141  // Check if source is an asset ID
142  if (substr($source, 0, 2) === 'a=') {
143  $source_asset = substr($source, 2);
144  $data = $this->_getAssetContents($source_asset);
145 
146  // Otherwise it's a URI
147  } else {
148  $data = file_get_contents($source);
149  }
150 
151  return $data;
152 
153  }//end getRawSourceData()
154 
155 
162  function getResultSet()
163  {
164  $GLOBALS['SQ_SYSTEM']->pm->startTimer($this);
165  // Get source string (to be used as cache key)
166  $source = $this->_getSource();
167 
168  if (!empty($source)) {
169 
170  // Check the local cache
171  if (!isset($this->_tmp[$source])) {
172 
173  // Try from the system cache
174  $result = parent::getResultSet($source);
175 
176  if ($result !== FALSE) {
177  $this->_tmp[$source] = $result;
178  } else {
179  $this->_tmp[$source] = $this->getItems();
180  parent::setResultSet($this->_tmp[$source], $source);
181  }
182  }
183  }
184  $GLOBALS['SQ_SYSTEM']->pm->stopTimer($this);
185  return (!empty($source)) ? $this->_tmp[$source] : Array();
186 
187  }//end getResultSet()
188 
189 
198  {
199  // User-specified object parameters/array values to pull out
200  $cherry_pick_nodes = $this->attr('nodes');
201  replace_global_keywords($cherry_pick_nodes);
202 
203  return preg_split("/[\s,]+/", $cherry_pick_nodes);
204 
205  }//end getUserDefinedJsNodes()
206 
207 
214  public function printBody()
215  {
216  // Get the source data
217  $original_json_data = $this->getRawSourceData();
218  if (empty($original_json_data)) return;
219 
220  // Run data through the parser
221  $results = $this->_parse($original_json_data);
222  if (empty($results)) return;
223 
224  // Re-encode data into JSON
225  $output_json = self::_encodeJson($results);
226  if (empty($output_json)) return;
227 
228  // For security, JSON output must have the correct content-type header (to
229  // prevent XSS attacks)
230  header('Content-type: application/json');
231 
232  echo self::_formatJsonText($output_json);
233 
234  }//end printBody()
235 
242  private static function _convertJsPathToArray($js_string)
243  {
244  return explode('.', preg_replace("/\[(\d)\]/", ".$1", $js_string));
245 
246  }//end _convertJsPathToArray()
247 
248 
257  private function _decodeJson($json_data)
258  {
259  // Prefer to use inbuilt PHP 5.3 json functions (or PHP extension)
260  if (function_exists('json_decode')) {
261  $result = json_decode($json_data, true);
262  } else {
263  require_once 'Services/JSON.php';
264  $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
265  $result = $json->decode($json_data);
266  }
267 
268  if ($result === NULL) {
269  trigger_error($this->_getJsonParseError() . ': ' .$this->name. ' (Id: #'.$this->id.')', E_USER_WARNING);
270  }
271 
272  return $result;
273 
274  }//end _decodeJson()
275 
276 
285  private function _encodeJson($arr)
286  {
287  // Prefer to use inbuilt PHP 5.3 json functions (or PHP extension)
288  if (function_exists('json_encode')) {
289  $result = json_encode($arr);
290  } else {
291  require_once 'Services/JSON.php';
292  $json = new Services_JSON();
293  $result = $json->encodeUnsafe($arr);
294 
295  if ($json->isError($result)) {
296  trigger_error($result->message, E_USER_WARNING);
297  $result = NULL;
298  }
299  }
300 
301  return $result;
302 
303  }//end decodeJson()
304 
305 
313  private static function _formatJsonText($json)
314  {
315  $pos = 0;
316  $indent = ' ';
317  $quoting = FALSE;
318  $result = '';
319 
320  for ($i = 0; $i <= strlen($json); $i++) {
321  $char = substr($json, $i, 1);
322  if ($char == '"') $quoting = !$quoting;
323 
324  if (!$quoting) {
325  if($char == '}' || $char == ']') {
326  $result .= "\n";
327  $pos --;
328  for ($j = 0; $j < $pos; $j++) $result .= $indent;
329  }
330  }
331 
332  $result .= $char;
333 
334  if (!$quoting) {
335  if ($char == ',' || $char == '{' || $char == '[') {
336  $result .= "\n";
337  if ($char == '{' || $char == '[') $pos ++;
338  for ($j = 0; $j < $pos; $j++) $result .= $indent;
339  }
340  }
341  }
342 
343  return $result;
344 
345  }//end _formatJsonText()
346 
347 
355  private static function _isAssociativeArray($a)
356  {
357  foreach (array_keys($a) as $key) {
358  if (is_string($key)) {
359  return TRUE;
360  }
361  }
362 
363  return FALSE;
364 
365  }//end _isAssociativeArray()
366 
367 
375  private static function _getArrayValueByJsPath($subject, $js_path_string)
376  {
377  $js_path = self::_convertJsPathToArray($js_path_string);
378  return self::_getArrayValueByKeyPath($subject, $js_path);
379  }
380 
381 
388  private static function _getArrayValueByKeyPath($subject, $key_path)
389  {
390  if (is_array($subject)) {
391 
392  // Key path still has more than a single key, we need to recurse further down
393  if (count($key_path) > 1) {
394  $next_level = array_shift($key_path);
395  if (array_key_exists($next_level, $subject)) {
396  return self::_getArrayValueByKeyPath($subject[$next_level], $key_path);
397  }
398  } else if ((count($key_path) === 1) && array_key_exists($key_path[0], $subject)) {
399  // We've reach the bottom level - return the value
400  return $subject[$key_path[0]];
401  }
402  }
403 
404  // Array value doesn't exist at this key path
405  return FALSE;
406 
407  }//end _getArrayValueByKeyPath()
408 
409 
418  private function _getAssetContents($assetid, $type_code='')
419  {
420  $asset_contents = '';
421  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid, $type_code);
422 
423  if ($asset == NULL) {
424  trigger_error('Unable to load asset (Id: #'.$assetid.')', E_USER_WARNING);
425  return NULL;
426  }
427 
428  // Important to require current user to have read permission on the source
429  // asset, because it can be dynamically changed if using parameter map
430  if (!$asset->readAccess()) {
431  trigger_error('No read permission for asset (Id: #'.$assetid.')', E_USER_WARNING);
432  return NULL;
433  }
434 
435  if ($asset instanceOf File){
436  $existing = $asset->getExistingFile();
437  if (empty($existing) || !is_file($existing['path'])) return $asset_contents;
438  $asset_contents = file_get_contents($existing['path']);
439  } else {
440  ob_start();
441  $asset->printBody();
442  $asset_contents = ob_get_contents();
443  ob_end_clean();
444  }
445 
446  return $asset_contents;
447 
448  }//end _getAssetContents()
449 
450 
457  private function _getJsonParseError()
458  {
459  $error = NULL;
460 
461  // Only available in PHP 5.3
462  if (function_exists('json_last_error')) {
463 
464  $error = json_last_error();
465 
466  switch($error)
467  {
468  case JSON_ERROR_NONE:
469  return "No error has occurred";
470  break;
471  case JSON_ERROR_DEPTH:
472  return "The maximum stack depth has been exceeded";
473  break;
474  case JSON_ERROR_CTRL_CHAR:
475  return "Control character error, possibly incorrectly encoded";
476  break;
477  case JSON_ERROR_STATE_MISMATCH:
478  return "Invalid or malformed JSON";
479  break;
480  case JSON_ERROR_SYNTAX:
481  return "Syntax error";
482  break;
483  }
484  }
485 
486  return "JSON parse error";
487 
488  }//end _getJsonParseError()
489 
490 
498  private function _getSource()
499  {
500  // Firstly check if replacement asset ID is specified in parameter map
501  $parameter_map = $this->getAttribute('parameter_map');
502  $parameter_map_source_asset = $parameter_map->getParameterValue('source_asset');
503  if (!empty($parameter_map_source_asset)) {
504 
505  // If specified value isn't a valid asset ID, don't try to use it; try and
506  // fall back to one of the set attribute values
507  if (assert_valid_assetid($parameter_map_source_asset, '', true, false)) {
508  return 'a=' . $parameter_map_source_asset;
509  }
510  }
511 
512  // Check if source asset is specified
513  $link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_NOTICE, '', TRUE, 'json_data_source');
514  if (!empty($link)) {
515  return 'a=' . $link['minorid'];
516  }
517 
518  // Try to get data from plain path/URI attribute
519  $uri = $this->attr('path');
520  if (!empty($uri)) {
521  replace_global_keywords($uri);
522  return $uri;
523  }
524 
525  return '';
526 
527  }//end _getSource()
528 
529 
538  private function _parse($json_data)
539  {
540  // Decode the JSON
541  $results = self::_decodeJson($json_data);
542  if (empty($results) || !is_array($results)){
543  return Array();
544  }
545 
546  // If a root object location is specified, drill down to that
547  $root_object = $this->attr('root_object');
548  replace_global_keywords($root_object);
549  if (!empty($root_object)) {
550  $results = self::_getArrayValueByJsPath($results, $root_object);
551  if ($results == FALSE || !is_array($results)) return Array();
552  }
553 
554  // If the parsed JSON is just a single object, wrap it in an array
555  if (self::_isAssociativeArray($results) || !is_array($results[0])) {
556  $results = Array($results);
557  }
558 
559  // User-specified object parameters/array values to pull out of the JSON
560  $cherry_pick_nodes = $this->getUserDefinedJsNodes();
561  if (!empty($cherry_pick_nodes)) {
562 
563  foreach ($results as &$asset) {
564 
565  foreach ($cherry_pick_nodes as $attr) {
566  $attr = trim($attr);
567  $value = self::_getArrayValueByJsPath($asset, $attr);
568 
569  if ($value !== FALSE) {
570  // Replace foo[1] with foo_1 as square brackets won't work in keywords
571  if (strpos($attr, '[')) {
572  $attr = preg_replace("/\[(\d)\]/", "_$1", $attr);
573  }
574  $asset[$attr] = $value;
575  }
576  }
577  }
578  }
579 
580  return $results;
581 
582  }//end _parse()
583 
584 
585 }//end class
586 
587 ?>