public function execute(array &$param_pool = null) { $result = new XMLElement($this->dsParamROOTELEMENT); // When DS is called out of the Frontend context, this will enable // {$root} and {$workspace} parameters to be evaluated if (empty($this->_env)) { $this->_env['env']['pool'] = array('root' => URL, 'workspace' => WORKSPACE); } try { require_once TOOLKIT . '/class.gateway.php'; require_once TOOLKIT . '/class.xsltprocess.php'; require_once CORE . '/class.cacheable.php'; $this->dsParamURL = $this->parseParamURL($this->dsParamURL); if (isset($this->dsParamXPATH)) { $this->dsParamXPATH = $this->__processParametersInString(stripslashes($this->dsParamXPATH), $this->_env); } // Builds a Default Stylesheet to transform the resulting XML with $stylesheet = new XMLElement('xsl:stylesheet'); $stylesheet->setAttributeArray(array('version' => '1.0', 'xmlns:xsl' => 'http://www.w3.org/1999/XSL/Transform')); $output = new XMLElement('xsl:output'); $output->setAttributeArray(array('method' => 'xml', 'version' => '1.0', 'encoding' => 'utf-8', 'indent' => 'yes', 'omit-xml-declaration' => 'yes')); $stylesheet->appendChild($output); $template = new XMLElement('xsl:template'); $template->setAttribute('match', '/'); $instruction = new XMLElement('xsl:copy-of'); // Namespaces if (isset($this->dsParamNAMESPACES) && is_array($this->dsParamNAMESPACES)) { foreach ($this->dsParamNAMESPACES as $name => $uri) { $instruction->setAttribute('xmlns' . ($name ? ":{$name}" : null), $uri); } } // XPath $instruction->setAttribute('select', $this->dsParamXPATH); $template->appendChild($instruction); $stylesheet->appendChild($template); $stylesheet->setIncludeHeader(true); $xsl = $stylesheet->generate(true); // Check for an existing Cache for this Datasource $cache_id = self::buildCacheID($this); $cache = Symphony::ExtensionManager()->getCacheProvider('remotedatasource'); $cachedData = $cache->check($cache_id); $writeToCache = null; $isCacheValid = true; $creation = DateTimeObj::get('c'); // Execute if the cache doesn't exist, or if it is old. if (!is_array($cachedData) || empty($cachedData) || time() - $cachedData['creation'] > $this->dsParamCACHE * 60) { if (Mutex::acquire($cache_id, $this->dsParamTIMEOUT, TMP)) { $ch = new Gateway(); $ch->init($this->dsParamURL); $ch->setopt('TIMEOUT', $this->dsParamTIMEOUT); // Set the approtiate Accept: headers depending on the format of the URL. if ($this->dsParamFORMAT == 'xml') { $ch->setopt('HTTPHEADER', array('Accept: text/xml, */*')); } elseif ($this->dsParamFORMAT == 'json') { $ch->setopt('HTTPHEADER', array('Accept: application/json, */*')); } elseif ($this->dsParamFORMAT == 'csv') { $ch->setopt('HTTPHEADER', array('Accept: text/csv, */*')); } self::prepareGateway($ch); $data = $ch->exec(); $info = $ch->getInfoLast(); Mutex::release($cache_id, TMP); $data = trim($data); $writeToCache = true; // Handle any response that is not a 200, or the content type does not include XML, JSON, plain or text if ((int) $info['http_code'] != 200 || !preg_match('/(xml|json|csv|plain|text)/i', $info['content_type'])) { $writeToCache = false; $result->setAttribute('valid', 'false'); // 28 is CURLE_OPERATION_TIMEOUTED if ($info['curl_error'] == 28) { $result->appendChild(new XMLElement('error', sprintf('Request timed out. %d second limit reached.', $timeout))); } else { $result->appendChild(new XMLElement('error', sprintf('Status code %d was returned. Content-type: %s', $info['http_code'], $info['content_type']))); } return $result; } else { if (strlen($data) > 0) { // Handle where there is `$data` // If it's JSON, convert it to XML if ($this->dsParamFORMAT == 'json') { try { require_once TOOLKIT . '/class.json.php'; $data = JSON::convertToXML($data); } catch (Exception $ex) { $writeToCache = false; $errors = array(array('message' => $ex->getMessage())); } } elseif ($this->dsParamFORMAT == 'csv') { try { require_once EXTENSIONS . '/remote_datasource/lib/class.csv.php'; $data = CSV::convertToXML($data); } catch (Exception $ex) { $writeToCache = false; $errors = array(array('message' => $ex->getMessage())); } } elseif ($this->dsParamFORMAT == 'txt') { $txtElement = new XMLElement('entry'); $txtElement->setValue(General::wrapInCDATA($data)); $data = $txtElement->generate(); $txtElement = null; } else { if (!General::validateXML($data, $errors, false, new XsltProcess())) { // If the XML doesn't validate.. $writeToCache = false; } } // If the `$data` is invalid, return a result explaining why if ($writeToCache === false) { $error = new XMLElement('errors'); $error->setAttribute('valid', 'false'); $error->appendChild(new XMLElement('error', __('Data returned is invalid.'))); foreach ($errors as $e) { if (strlen(trim($e['message'])) == 0) { continue; } $error->appendChild(new XMLElement('item', General::sanitize($e['message']))); } $result->appendChild($error); return $result; } } elseif (strlen($data) == 0) { // If `$data` is empty, set the `force_empty_result` to true. $this->_force_empty_result = true; } } } else { // Failed to acquire a lock $result->appendChild(new XMLElement('error', __('The %s class failed to acquire a lock.', array('<code>Mutex</code>')))); } } else { // The cache is good, use it! $data = trim($cachedData['data']); $creation = DateTimeObj::get('c', $cachedData['creation']); } // Visit the data $this->exposeData($data); // If `$writeToCache` is set to false, invalidate the old cache if it existed. if (is_array($cachedData) && !empty($cachedData) && $writeToCache === false) { $data = trim($cachedData['data']); $isCacheValid = false; $creation = DateTimeObj::get('c', $cachedData['creation']); if (empty($data)) { $this->_force_empty_result = true; } } // If `force_empty_result` is false and `$result` is an instance of // XMLElement, build the `$result`. if (!$this->_force_empty_result && is_object($result)) { $proc = new XsltProcess(); $ret = $proc->process($data, $xsl); if ($proc->isErrors()) { $result->setAttribute('valid', 'false'); $error = new XMLElement('error', __('Transformed XML is invalid.')); $result->appendChild($error); $errors = new XMLElement('errors'); foreach ($proc->getError() as $e) { if (strlen(trim($e['message'])) == 0) { continue; } $errors->appendChild(new XMLElement('item', General::sanitize($e['message']))); } $result->appendChild($errors); $result->appendChild(new XMLElement('raw-data', General::wrapInCDATA($data))); } elseif (strlen(trim($ret)) == 0) { $this->_force_empty_result = true; } else { if ($this->dsParamCACHE > 0 && $writeToCache) { $cache->write($cache_id, $data, $this->dsParamCACHE); } $result->setValue(PHP_EOL . str_repeat("\t", 2) . preg_replace('/([\\r\\n]+)/', "\$1\t", $ret)); $result->setAttribute('status', $isCacheValid === true ? 'fresh' : 'stale'); $result->setAttribute('cache-id', $cache_id); $result->setAttribute('creation', $creation); } } } catch (Exception $e) { $result->appendChild(new XMLElement('error', $e->getMessage())); } if ($this->_force_empty_result) { $result = $this->emptyXMLSet(); } $result->setAttribute('url', General::sanitize($this->dsParamURL)); return $result; }