Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
data_source_xml.inc
1 <?php
18 require_once SQ_CORE_PACKAGE_PATH.'/../include/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 {
40  protected $_tree_level = 0;
41 
42 
48  protected $_record_tree_level = 0;
49 
50 
56  protected $_non_empty_record = FALSE;
57 
58 
64  protected $_result = Array();
65 
66 
73  protected $_inside_relevant_tag = FALSE;
74 
75 
81  protected $_inside_root_tag = FALSE;
82 
83 
89  protected $_current_tag_name = '';
90 
91 
97  protected $_current_attributes = Array();
98 
99 
105  protected $_record_set_index = 0;
106 
107 
114  protected $_new_teg = FALSE;
115 
116 
121  protected $_complex_elements = Array();
122 
123 
130  function __construct($assetid=0)
131  {
132  parent::__construct($assetid);
133 
134  }//end constructor
135 
136 
144  public function _getAllowedLinks()
145  {
146  // any link is allowed
147  $allowed_link['asset']['card'] = 'M';
148  $allowed_link['asset']['exclusive'] = FALSE;
149 
150  $links[SQ_LINK_TYPE_1] = $allowed_link;
151  $links[SQ_LINK_TYPE_2] = $allowed_link;
152  $links[SQ_LINK_TYPE_3] = $allowed_link;
153  $links[SQ_LINK_NOTICE] = $allowed_link;
154 
155  return $links;
156 
157  }//end _getAllowedLinks()
158 
159 
166  function getResultSet()
167  {
168  $GLOBALS['SQ_SYSTEM']->pm->startTimer($this);
169  // use feed url as the cache key
170  $url = $this->_getFeedURL();
171 
172  if (!empty($url)) {
173  $hash = md5($url);
174 
175  // Check the local cache
176  if (!isset($this->_tmp[$hash])) {
177  // Try from the system cache
178  $result = parent::getResultSet($hash);
179 
180  if ($result !== FALSE) {
181  $this->_tmp[$hash] = $result;
182  } else {
183  $this->_tmp[$hash] = $this->getItems();
184  if ($this->_tmp[$hash] !== FALSE) {
185  parent::setResultSet($this->_tmp[$hash], $hash);
186  }
187  }
188  }
189  }
190  $GLOBALS['SQ_SYSTEM']->pm->stopTimer($this);
191  return (!empty($url)) ? $this->_tmp[$hash] : Array();
192 
193  }//end getResultSet()
194 
195 
202  function getItems()
203  {
204  $result = $this->parseXML();
205  return $result;
206 
207  }//end getItems()
208 
209 
218  function openFeed($url)
219  {
220 
221  if (isset($this->_tmp['urlcontents'][$url]) === TRUE) {
222  return $this->_tmp['urlcontents'][$url];
223  }
224 
225  $options = array(
226  'RETURNTRANSFER' => 1,
227  'USERAGENT' => SQ_SYSTEM_LONG_NAME,
228  );
229 
230  $responseDetails = fetch_url($url, $options);
231 
232 
233  if ($responseDetails['errornumber'] != 0) {
234  trigger_localised_error('CMS0063', E_USER_WARNING, $url, $responseDetails['errorstring']);
235  return FALSE;
236  }
245  $response = $responseDetails['response'];
246 
247  $this->_tmp['urlcontents'][$url] = $response;
248 
249  return $response;
250 
251  }//end openFeed()
252 
253 
260  function parseXML()
261  {
262  $url = html_entity_decode($this->_tmp['url_string']);
263  $content = $this->openFeed($url);
264 
265  $content = $this->_addCDATA($content);
266 
267  return $this->_createParser($content);
268 
269  }//end parseXML()
270 
271 
278  function getXMLContent()
279  {
280  $url = html_entity_decode(isset($this->_tmp['url_string']) ? $this->_tmp['url_string'] : $this->attr('url'));
281  $content = $this->openFeed($url);
282 
283  return $this->_addCDATA($content);
284 
285  }//end getXMLContent()
286 
287 
296  function _createParser($content)
297  {
298  if (!empty($content)) {
299 
300  // hack time : #4588 Empty tags are not being parsed in XML Data Source
301  // 1 - if we have <abc /> tag we change it to <abc>\n</abc>
302  // 2 - if we have <abc></abc> tag we change it to <abc>\n</abc>
303  $required_tags = $this->_getRequiredTags();
304  if(!empty($required_tags)) {
305  foreach($required_tags as $index => $required_tag) {
306  $pattern = '|(<'.$required_tag.')([\s]+[^>]*)/>|i';
307  $replace = "<".$required_tag."$2>\n</".$required_tag.">";
308  $content = preg_replace($pattern, $replace, $content);
309 
310  $pattern = '|(<'.$required_tag.')([\s]+[^>]*)></'.$required_tag.'>|i';
311  $replace = "<".$required_tag."$2>\n</".$required_tag.">";
312 
313  $content = preg_replace($pattern, $replace, $content);
314 
315  }
316  }
317 
318  $xml_parser = xml_parser_create();
319  // stop the parser upper-casing everything
320  xml_parser_set_option ($xml_parser, XML_OPTION_CASE_FOLDING, 0);
321  xml_set_character_data_handler ($xml_parser, Array(&$this,'characterData') );
322  xml_set_element_handler($xml_parser, Array(&$this,'startElement'), Array(&$this,'endElement'));
323 
324  if (!xml_parse($xml_parser, $content, TRUE)) {
325  trigger_localised_error('CORE0258', E_USER_WARNING, xml_error_string(xml_get_error_code($xml_parser)).', line '.xml_get_current_line_number($xml_parser).', column '.xml_get_current_column_number($xml_parser).' (byte '.xml_get_current_byte_index($xml_parser).')', htmlentities($content, ENT_COMPAT, SQ_CONF_DEFAULT_CHARACTER_SET));
326  return FALSE;
327  }
328  xml_parser_free($xml_parser);
329  return $this->_result;
330  }
331 
332  return Array();
333 
334  }//end _createParser()
335 
336 
345  function _checkRootTag($name)
346  {
347  $root_tag = $this->attr('root_tag');
348 
349  if (!$this->_inside_root_tag) {
350  if (empty($root_tag)) {
351  $this->_inside_root_tag = TRUE;
352  } else if ($name === $root_tag) {
353  $this->_inside_root_tag = TRUE;
354  } else {
355  $this->_inside_root_tag = FALSE;
356  }
357  } else {
358  // already inside the root tag
359  if (empty($root_tag)) {
360  // handle empty root tag
361  $this->_inside_root_tag = TRUE;
362  } else if ($name === $root_tag) {
363  // handle the closing root tag
364  $this->_inside_root_tag = FALSE;
365  }
366  }
367 
368  }//end _checkRootTag()
369 
370 
381  function startElement($parser, $name, $attributes=FALSE)
382  {
383  // Increment the XML tree level tracker one level up
384  $this->_tree_level++;
385 
386  // Count the number of non-text elements the parsed element nests
387  foreach($this->_complex_elements as $element => $val) {
388  if ($this->_complex_elements[$element] >= 0) $this->_complex_elements[$element]++;
389  }
390  if (!isset($this->_complex_elements[$name])) {
391  $this->_complex_elements[$name] = 0;
392  }
393 
394  if (!empty($name)) {
395  // check that we are inside the root tag
396  $this->_checkRootTag($name);
397 
398  if ($this->_inside_root_tag) {
399  $required_tags = $this->_getRequiredTags();
400  for ($i=0; $i<count($required_tags); $i++) {
401  if ($name === $required_tags[$i]) {
402  $this->_inside_relevant_tag = TRUE;
403  $this->_current_tag_name = $name;
404  $this->_new_tag = TRUE;
405 
406  // Set record tree level to where required elemets are
407  $this->_record_tree_level = $this->_tree_level;
408 
409  // As far as required tags are concerned current record is non empty for sure
410  $this->_non_empty_record = TRUE;
411 
412  if ($attributes) {
413  $this->_current_attributes = $attributes;
414  } else {
415  $this->_current_attributes = FALSE;
416  }
417  }
418  }
419 
420  // If the current tree level is less than tree level where records are then we are finished with the current record
421  // Or even if the current tree level is on same level where the relevent record is,
422  // if the current element complex then it will have to goto new record
423  if ($this->_non_empty_record && ($this->_tree_level < $this->_record_tree_level || ($this->_tree_level == $this->_record_tree_level && $this->_complex_elements[$this->_current_tag_name] == -1))) {
424  $this->_record_set_index++;
425 
426  // Don't know if required tags are going to be there in next recordset
427  $this->_non_empty_record = FALSE;
428  }
429 
430  }//end if inside root node
431  }
432 
433  }//end startElement()
434 
435 
445  function characterData($parser, $data)
446  {
447  if ($this->_inside_relevant_tag) {
448  if (!isset($this->_result[$this->_record_set_index][$this->_current_tag_name])) {
449  $this->_result[$this->_record_set_index][$this->_current_tag_name] = $data;
450  }
451  // If value for this tag is already set even when it is new tag then it means there are multiple tags with same name in the record
452  // Merge the values using the given delimiter
453  else if ($this->_new_tag) {
454  $this->_result[$this->_record_set_index][$this->_current_tag_name] .= $this->attr('data_delimiter') . $data;
455  }
456  // Being here implies that this function handler has been called more than once for data in the same tag.
457  // Merge the data to get the whole thing.
458  // See bug #4113 - When parser encounters special content in data like &amp;, &apos;, etc., it stops scanning and calls the data handler,
459  // and then calls the handler again for the remaining data in the tag.
460  else {
461  $this->_result[$this->_record_set_index][$this->_current_tag_name] .= $data;
462  }
463 
464  $required_attributes = $this->_getRequiredAttributes();
465  // add attribute values if required
466  if (!empty($required_attributes) && $this->_current_attributes) {
467  foreach ($required_attributes as $tag_name => $attr_name) {
468  if ($this->_current_tag_name === $tag_name) {
469  foreach ($this->_current_attributes as $name => $value) {
470  for ($i=0; $i<count($attr_name); $i++) {
471  if ($attr_name[$i] === $name) {
472  $this->_result[$this->_record_set_index][$this->_current_tag_name.':'.$attr_name[$i]] = $value;
473  }
474  }
475  }
476  }
477  }
478  }
479  }
480 
481  $this->_new_tag = FALSE;
482 
483  }//end characterData()
484 
485 
495  function endElement($parser, $name)
496  {
497  // check that we are outside of the root tag
498  $this->_checkRootTag($name);
499 
500  // If the element nests 1 or more non-text elements then it is complex
501  // -1 means complex and -2 means otherwise
502  if ($this->_complex_elements[$name] >= 0) {
503  $this->_complex_elements[$name] = $this->_complex_elements[$name] > 0 ? -1 : -2;
504 
505  }
506  // Move the XML tree level tracker one level down on the closing of the element
507  $this->_tree_level--;
508 
509  $this->_inside_relevant_tag = FALSE;
510 
511  }//end endElement()
512 
513 
520  function _getRequiredTags()
521  {
522  return unserialize(html_entity_decode($this->attr('tags')));
523 
524  }//end _getRequiredTags()
525 
526 
534  {
535  return unserialize(html_entity_decode($this->attr('tag_attributes')));
536 
537  }//end _getRequiredAttributes()
538 
539 
546  function _getFeedURL()
547  {
548  $url = $this->attr('url');
549  $keyword_wrapper = '%%';
550  $keyword_pattern = '('.$keyword_wrapper.'([a-zA-Z_\-0-9\.]+)'.$keyword_wrapper.')';
551 
552  // insert the dynamic parameters into the url
553  preg_match_all ('/'.$keyword_pattern.'/', $url, $matches, PREG_PATTERN_ORDER);
554  if (empty($matches[1])) {
555  $this->_tmp['url_string'] = $url;
556  return $this->_tmp['url_string'];
557  }
558 
559  $raw_keywords =& $matches[1];
560  $keywords =& $matches[2];
561 
562  foreach ($keywords as $keyword) {
563  $value = $this->getDynamicVariableValue($keyword);
564  $replacements[] = str_replace('$', '\$', $value);
565  $patterns[] = '/('.$keyword_wrapper.$keyword.$keyword_wrapper.')/';
566  }
567 
568  $url = preg_replace($patterns, $replacements, $url);
569 
570  $this->_tmp['url_string'] = $url;
571  return $this->_tmp['url_string'];
572 
573  }//end _getFeedURL()
574 
575 
582  function getURLString()
583  {
584  return isset($this->_tmp['url_string']) ? $this->_tmp['url_string'] : '';
585 
586  }//end getURLString()
587 
588 
597  function getDynamicVariableValue($variable_name)
598  {
599  $dynamic_vars = $this->attr('dynamic_vars');
600  if (!isset($dynamic_vars[$variable_name])) return '';
601 
602  $parameter_map = $this->getAttribute('parameter_map');
603  $value = $parameter_map->getParameterValue($variable_name);
604 
605  if (empty($value)) {
606  // Default values are already urlencoded
607  return array_get_index($dynamic_vars, $variable_name, '');
608  }
609 
610  return urlencode($value);
611 
612  }//end getDynamicVariableValue()
613 
614 
623  function _addCDATA($content)
624  {
625  $cdata_tags = unserialize(html_entity_decode($this->attr('cdata_tags')));
626  if (empty($cdata_tags)) return $content;
627  $cdata_tags = array_unique($cdata_tags);
628 
629  foreach($cdata_tags as $cdata_tag) {
630  if (!$cdata_tag) continue;
631 
632  // Replace only if the element data is not already wrapped with CDATA
633  $replace_pattern = "/(<$cdata_tag(\s[^>]*?)*?(?<!\/)>)(?!<\!\[CDATA\[)(.*?)(?<!\]\]>)(<\/$cdata_tag\s*>)/ms";
634  $replacement = "$1<![CDATA[$3]]>$4";
635  $content = preg_replace($replace_pattern, $replacement, $content);
636  }
637 
638  return $content;
639  }
640 
641 
648  public function printFrontend()
649  {
650  $xsl_file = NULL;
651  $xsl_file_id = $this->attr('xslt_file');
652 
653  if ($xsl_file_id) {
654  $xsl_file = $GLOBALS['SQ_SYSTEM']->am->getAsset($xsl_file_id);
655 
656  if (!is_null($xsl_file) && class_exists('XSLTProcessor')) {
658  }
659  }//end if
660 
661 
662  }//end printFrontend()
663 
664 
671  public function printBody()
672  {
673  // Find the XSL Stylesheet
674  $xsl_file_id = $this->attr('xslt_file');
675  $feed_url = $this->_getFeedURL();
676 
677  if (!empty($feed_url) && $xsl_file_id) {
678  $xsl_file = $GLOBALS['SQ_SYSTEM']->am->getAsset($xsl_file_id);
679 
680  if (!is_null($xsl_file)) {
681  // Do transformation
682  $xml = new DOMDocument();
683  $xml->loadXML($this->getXMLContent());
684  $xsl = new DOMDocument();
685  $xsl->loadXML($xsl_file->getContent());
686 
687  $parser = new XSLTProcessor();
688  $parser->importStylesheet($xsl);
689 
690  $content = $parser->transformToXML($xml);
691  echo $content;
692  }
693  }//end if
694 
695  }//end printBody()
696 
697 }//end class
698 
699 ?>