public function execute(array &$param_pool = null) { $result = new XMLElement($this->dsParamROOTELEMENT); $this->dsParamURL = $this->parseParamURL($this->dsParamURL); if (isset($this->dsParamXPATH)) { $this->dsParamXPATH = $this->__processParametersInString($this->dsParamXPATH, $this->_env); } $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->dsParamFILTERS) && is_array($this->dsParamFILTERS)) { foreach ($this->dsParamFILTERS 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); $cache_id = md5($this->dsParamURL . serialize($this->dsParamFILTERS) . $this->dsParamXPATH); $cache = new Cacheable(Symphony::Database()); $cachedData = $cache->read($cache_id); $writeToCache = false; $valid = true; $creation = DateTimeObj::get('c'); $timeout = isset($this->dsParamTIMEOUT) ? (int) max(1, $this->dsParamTIMEOUT) : 6; // 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, $timeout, TMP)) { $ch = new Gateway(); $ch->init($this->dsParamURL); $ch->setopt('TIMEOUT', $timeout); $ch->setopt('HTTPHEADER', array('Accept: text/xml, */*')); $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, plain or text if ((int) $info['http_code'] !== 200 || !preg_match('/(xml|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; // Handle where there is `$data` } elseif (strlen($data) > 0) { // If the XML doesn't validate.. if (!General::validateXML($data, $errors, false, new XsltProcess())) { $writeToCache = false; } // If the `$data` is invalid, return a result explaining why if ($writeToCache === false) { $element = new XMLElement('errors'); $result->setAttribute('valid', 'false'); $result->appendChild(new XMLElement('error', __('Data returned is invalid.'))); foreach ($errors as $e) { if (strlen(trim($e['message'])) == 0) { continue; } $element->appendChild(new XMLElement('item', General::sanitize($e['message']))); } $result->appendChild($element); return $result; } // If `$data` is empty, set the `force_empty_result` to true. } elseif (strlen($data) == 0) { $this->_force_empty_result = true; } // Failed to acquire a lock } else { $result->appendChild(new XMLElement('error', __('The %s class failed to acquire a lock, check that %s exists and is writable.', array('<code>Mutex</code>', '<code>' . TMP . '</code>')))); } // The cache is good, use it! } else { $data = trim($cachedData['data']); $creation = DateTimeObj::get('c', $cachedData['creation']); } // 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']); $valid = 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); $element = new XMLElement('errors'); foreach ($proc->getError() as $e) { if (strlen(trim($e['message'])) == 0) { continue; } $element->appendChild(new XMLElement('item', General::sanitize($e['message']))); } $result->appendChild($element); } elseif (strlen(trim($ret)) == 0) { $this->_force_empty_result = true; } else { if ($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', $valid === true ? 'fresh' : 'stale'); $result->setAttribute('creation', $creation); } } return $result; }
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; }
} } else { $xml = trim($cachedData['data']); $creation = DateTimeObj::get('c', $cachedData['creation']); } // If `force_empty_result` is false and `$result` is not an instance of // XMLElement, build the `$result`. if (!$this->_force_empty_result && is_object($result)) { $proc = new XsltProcess(); $ret = $proc->process($xml, $xsl); if ($proc->isErrors()) { $result->setAttribute('valid', 'false'); $error = new XMLElement('error', __('XML returned is invalid.')); $result->appendChild($error); $element = new XMLElement('errors'); foreach ($proc->getError() as $e) { if (strlen(trim($e['message'])) == 0) { continue; } $element->appendChild(new XMLElement('item', General::sanitize($e['message']))); } $result->appendChild($element); } else { if (strlen(trim($ret)) == 0) { $this->_force_empty_result = true; } else { if ($writeToCache) { $cache->write($cache_id, $xml); } $result->setValue(self::CRLF . preg_replace('/([\\r\\n]+)/', '$1 ', $ret)); $result->setAttribute('status', $valid === true ? 'fresh' : 'stale');
/** * Returns an iterator of errors from the `XsltProcess`. Use this function * inside a loop to get all the errors that occurring when transforming * `$this->_xml` with `$this->_xsl`. * * @return array * An associative array containing the errors details from the * `XsltProcessor` */ public function getError() { return $this->Proc->getError(); }
/** * * Builds the content view */ public function view() { // _context[0] => entry values // _context[1] => fieldId if (!is_array($this->_context) || empty($this->_context)) { $this->_Result->appendChild(new XMLElement('error', __('Parameters not found'))); return; } else { if (count($this->_context) < self::NUMBER_OF_URL_PARAMETERS) { $this->_Result->appendChild(new XMLElement('error', __('Not enough parameters'))); return; } else { if (count($this->_context) > self::NUMBER_OF_URL_PARAMETERS) { $this->_Result->appendChild(new XMLElement('error', __('Too many parameters'))); return; } } } $entriesId = explode(',', MySQL::cleanValue($this->_context[0])); $entriesId = array_map(array('General', 'intval'), $entriesId); if (!is_array($entriesId) || empty($entriesId)) { $this->_Result->appendChild(new XMLElement('error', __('No entry no found'))); return; } $parentFieldId = General::intval($this->_context[1]); if ($parentFieldId < 1) { $this->_Result->appendChild(new XMLElement('error', __('Parent field id not valid'))); return; } $parentField = $this->fieldManager->fetch($parentFieldId); if (!$parentField || empty($parentField)) { $this->_Result->appendChild(new XMLElement('error', __('Parent field not found'))); return; } if ($parentField->get('type') != 'entry_relationship') { $this->_Result->appendChild(new XMLElement('error', __('Parent field is `%s`, not `entry_relationship`', array($parentField->get('type'))))); return; } $includedElements = $this->parseIncludedElements($parentField); $xmlParams = self::getXmlParams(); // Get entries one by one since they may belong to // different sections, which prevents us from // passing an array of entryId. foreach ($entriesId as $key => $entryId) { $entry = $this->entryManager->fetch($entryId); if (empty($entry)) { $li = new XMLElement('li', null, array('data-entry-id' => $entryId)); $header = new XMLElement('header', null, array('class' => 'frame-header')); $title = new XMLElement('h4'); $title->appendChild(new XMLElement('strong', __('Entry %s not found', array($entryId)))); $header->appendChild($title); $options = new XMLElement('div', null, array('class' => 'destructor')); if ($parentField->is('allow_link')) { $options->appendChild(new XMLElement('a', __('Un-link'), array('class' => 'unlink', 'data-unlink' => $entryId))); } $header->appendChild($options); $li->appendChild($header); $this->_Result->appendChild($li); } else { $entry = $entry[0]; $entryData = $entry->getData(); $entrySection = $this->sectionManager->fetch($entry->get('section_id')); $entryVisibleFields = $entrySection->fetchVisibleColumns(); $entryFields = $entrySection->fetchFields(); $entrySectionHandle = $this->getSectionName($entry, 'handle'); $li = new XMLElement('li', null, array('data-entry-id' => $entryId, 'data-section' => $entrySectionHandle, 'data-section-id' => $entrySection->get('id'))); $header = new XMLElement('header', null, array('class' => 'frame-header')); $title = new XMLElement('h4'); $title->appendChild(new XMLElement('strong', $this->getEntryTitle($entry, $entryVisibleFields, $entryFields))); $title->appendChild(new XMLElement('span', $this->getSectionName($entry))); $header->appendChild($title); $options = new XMLElement('div', null, array('class' => 'destructor')); if ($parentField->is('allow_edit')) { $title->setAttribute('data-edit', $entryId); $options->appendChild(new XMLElement('a', __('Edit'), array('class' => 'edit', 'data-edit' => $entryId))); } if ($parentField->is('allow_delete')) { $options->appendChild(new XMLElement('a', __('Delete'), array('class' => 'delete', 'data-delete' => $entryId))); } if ($parentField->is('allow_link')) { $options->appendChild(new XMLElement('a', __('Replace'), array('class' => 'unlink', 'data-replace' => $entryId))); } if ($parentField->is('allow_delete') || $parentField->is('allow_link')) { $options->appendChild(new XMLElement('a', __('Un-link'), array('class' => 'unlink', 'data-unlink' => $entryId))); } $header->appendChild($options); $li->appendChild($header); $xslFilePath = WORKSPACE . '/er-templates/' . $entrySectionHandle . '.xsl'; if (!empty($entryData) && !!@file_exists($xslFilePath)) { $xmlData = new XMLElement('data'); $xmlData->setIncludeHeader(true); $xml = new XMLElement('entry'); $xml->setAttribute('id', $entryId); $xmlData->appendChild($xmlParams); $xmlData->appendChild($xml); foreach ($entryData as $fieldId => $data) { $filteredData = array_filter($data, function ($value) { return $value != null; }); if (empty($filteredData)) { continue; } $field = $entryFields[$fieldId]; $fieldName = $field->get('element_name'); $fieldIncludedElement = $includedElements[$entrySectionHandle]; if (FieldEntry_relationship::isFieldIncluded($fieldName, $fieldIncludedElement)) { $fieldIncludableElements = $field->fetchIncludableElements(); if ($field instanceof FieldEntry_relationship) { $fieldIncludableElements = null; } if (!empty($fieldIncludableElements) && count($fieldIncludableElements) > 1) { foreach ($fieldIncludableElements as $fieldIncludableElement) { $submode = preg_replace('/^' . $fieldName . '\\s*\\:\\s*/i', '', $fieldIncludableElement, 1); $field->appendFormattedElement($xml, $data, false, $submode, $entryId); } } else { $field->appendFormattedElement($xml, $data, false, null, $entryId); } } } $indent = false; $mode = $parentField->get('mode'); if (isset($_REQUEST['debug'])) { $mode = 'debug'; } if ($mode == 'debug') { $indent = true; } $xmlMode = empty($mode) ? '' : 'mode="' . $mode . '"'; $xmlString = $xmlData->generate($indent, 0); $xsl = '<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="' . str_replace('\\', '/', $xslFilePath) . '"/> <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="no" /> <xsl:template match="/"> <xsl:apply-templates select="/data" ' . $xmlMode . ' /> </xsl:template> <xsl:template match="/data" ' . $xmlMode . '> <xsl:apply-templates select="entry" ' . $xmlMode . ' /> </xsl:template> <xsl:template match="/data" mode="debug"> <xsl:copy-of select="/" /> </xsl:template> </xsl:stylesheet>'; $xslt = new XsltProcess(); $result = $xslt->process($xmlString, $xsl, $this->params); if ($mode == 'debug') { $result = '<pre><code>' . str_replace('<', '<', str_replace('>', '>', $xmlString)) . '</code></pre>'; } if ($xslt->isErrors()) { $error = $xslt->getError(); $result = $error[1]['message']; } if (!!$xslt && strlen($result) > 0) { $content = new XMLElement('div', $result, array('class' => 'content')); $li->appendChild($content); } } $this->_Result->appendChild($li); } } }