PageRenderTime 24ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/symphony/lib/toolkit/data-sources/class.datasource.dynamic_xml.php

https://github.com/nils-werner/symphony-2
PHP | 200 lines | 131 code | 45 blank | 24 comment | 37 complexity | abf92850aeda1ffa3de77e0e7571f88a MD5 | raw file
  1. <?php
  2. /**
  3. * @package data-sources
  4. */
  5. /**
  6. * The `DynamicXMLDatasource` allows a user to retrieve XML from an URL.
  7. * This datasource supports namespaces, partial results using XPath and
  8. * caching the result for a number of minutes.
  9. *
  10. * @since Symphony 2.3
  11. */
  12. require_once(TOOLKIT . '/class.gateway.php');
  13. require_once(TOOLKIT . '/class.xsltprocess.php');
  14. require_once(CORE . '/class.cacheable.php');
  15. Class DynamicXMLDatasource extends Datasource {
  16. public function execute(array &$param_pool = null) {
  17. $result = new XMLElement($this->dsParamROOTELEMENT);
  18. $this->dsParamURL = $this->parseParamURL($this->dsParamURL);
  19. if(isset($this->dsParamXPATH)) $this->dsParamXPATH = $this->__processParametersInString($this->dsParamXPATH, $this->_env);
  20. $stylesheet = new XMLElement('xsl:stylesheet');
  21. $stylesheet->setAttributeArray(array('version' => '1.0', 'xmlns:xsl' => 'http://www.w3.org/1999/XSL/Transform'));
  22. $output = new XMLElement('xsl:output');
  23. $output->setAttributeArray(array('method' => 'xml', 'version' => '1.0', 'encoding' => 'utf-8', 'indent' => 'yes', 'omit-xml-declaration' => 'yes'));
  24. $stylesheet->appendChild($output);
  25. $template = new XMLElement('xsl:template');
  26. $template->setAttribute('match', '/');
  27. $instruction = new XMLElement('xsl:copy-of');
  28. // Namespaces
  29. if(isset($this->dsParamFILTERS) && is_array($this->dsParamFILTERS)){
  30. foreach($this->dsParamFILTERS as $name => $uri) $instruction->setAttribute('xmlns' . ($name ? ":{$name}" : NULL), $uri);
  31. }
  32. // XPath
  33. $instruction->setAttribute('select', $this->dsParamXPATH);
  34. $template->appendChild($instruction);
  35. $stylesheet->appendChild($template);
  36. $stylesheet->setIncludeHeader(true);
  37. $xsl = $stylesheet->generate(true);
  38. $cache_id = md5($this->dsParamURL . serialize($this->dsParamFILTERS) . $this->dsParamXPATH);
  39. $cache = new Cacheable(Symphony::Database());
  40. $cachedData = $cache->check($cache_id);
  41. $writeToCache = false;
  42. $valid = true;
  43. $creation = DateTimeObj::get('c');
  44. $timeout = (isset($this->dsParamTIMEOUT)) ? (int)max(1, $this->dsParamTIMEOUT) : 6;
  45. // Execute if the cache doesn't exist, or if it is old.
  46. if(
  47. (!is_array($cachedData) || empty($cachedData)) // There's no cache.
  48. || (time() - $cachedData['creation']) > ($this->dsParamCACHE * 60) // The cache is old.
  49. ){
  50. if(Mutex::acquire($cache_id, $timeout, TMP)) {
  51. $ch = new Gateway;
  52. $ch->init($this->dsParamURL);
  53. $ch->setopt('TIMEOUT', $timeout);
  54. $ch->setopt('HTTPHEADER', array('Accept: text/xml, */*'));
  55. $data = $ch->exec();
  56. $info = $ch->getInfoLast();
  57. Mutex::release($cache_id, TMP);
  58. $data = trim($data);
  59. $writeToCache = true;
  60. // Handle any response that is not a 200, or the content type does not include XML, plain or text
  61. if((int)$info['http_code'] != 200 || !preg_match('/(xml|plain|text)/i', $info['content_type'])){
  62. $writeToCache = false;
  63. $result->setAttribute('valid', 'false');
  64. // 28 is CURLE_OPERATION_TIMEOUTED
  65. if($info['curl_error'] == 28) {
  66. $result->appendChild(
  67. new XMLElement('error',
  68. sprintf('Request timed out. %d second limit reached.', $timeout)
  69. )
  70. );
  71. }
  72. else{
  73. $result->appendChild(
  74. new XMLElement('error',
  75. sprintf('Status code %d was returned. Content-type: %s', $info['http_code'], $info['content_type'])
  76. )
  77. );
  78. }
  79. return $result;
  80. }
  81. // Handle where there is `$data`
  82. else if(strlen($data) > 0) {
  83. // If the XML doesn't validate..
  84. if(!General::validateXML($data, $errors, false, new XsltProcess)) {
  85. $writeToCache = false;
  86. }
  87. // If the `$data` is invalid, return a result explaining why
  88. if($writeToCache === false) {
  89. $element = new XMLElement('errors');
  90. $result->setAttribute('valid', 'false');
  91. $result->appendChild(new XMLElement('error', __('Data returned is invalid.')));
  92. foreach($errors as $e) {
  93. if(strlen(trim($e['message'])) == 0) continue;
  94. $element->appendChild(new XMLElement('item', General::sanitize($e['message'])));
  95. }
  96. $result->appendChild($element);
  97. return $result;
  98. }
  99. }
  100. // If `$data` is empty, set the `force_empty_result` to true.
  101. else if(strlen($data) == 0){
  102. $this->_force_empty_result = true;
  103. }
  104. }
  105. // Failed to acquire a lock
  106. else {
  107. $result->appendChild(
  108. new XMLElement('error', __('The %s class failed to acquire a lock, check that %s exists and is writable.', array(
  109. '<code>Mutex</code>',
  110. '<code>' . TMP . '</code>'
  111. )))
  112. );
  113. }
  114. }
  115. // The cache is good, use it!
  116. else {
  117. $data = trim($cachedData['data']);
  118. $creation = DateTimeObj::get('c', $cachedData['creation']);
  119. }
  120. // If `$writeToCache` is set to false, invalidate the old cache if it existed.
  121. if(is_array($cachedData) && !empty($cachedData) && $writeToCache === false) {
  122. $data = trim($cachedData['data']);
  123. $valid = false;
  124. $creation = DateTimeObj::get('c', $cachedData['creation']);
  125. if(empty($data)) $this->_force_empty_result = true;
  126. }
  127. // If `force_empty_result` is false and `$result` is an instance of
  128. // XMLElement, build the `$result`.
  129. if(!$this->_force_empty_result && is_object($result)) {
  130. $proc = new XsltProcess;
  131. $ret = $proc->process($data, $xsl);
  132. if($proc->isErrors()) {
  133. $result->setAttribute('valid', 'false');
  134. $error = new XMLElement('error', __('Transformed XML is invalid.'));
  135. $result->appendChild($error);
  136. $element = new XMLElement('errors');
  137. foreach($proc->getError() as $e) {
  138. if(strlen(trim($e['message'])) == 0) continue;
  139. $element->appendChild(new XMLElement('item', General::sanitize($e['message'])));
  140. }
  141. $result->appendChild($element);
  142. }
  143. else if(strlen(trim($ret)) == 0) {
  144. $this->_force_empty_result = true;
  145. }
  146. else {
  147. if($writeToCache) $cache->write($cache_id, $data, $this->dsParamCACHE);
  148. $result->setValue(PHP_EOL . str_repeat("\t", 2) . preg_replace('/([\r\n]+)/', "$1\t", $ret));
  149. $result->setAttribute('status', ($valid === true ? 'fresh' : 'stale'));
  150. $result->setAttribute('creation', $creation);
  151. }
  152. }
  153. return $result;
  154. }
  155. }