Skip to content

Commit d896f20

Browse files
committed
Merge branch 'master' of github.com:symphonycms/remote_datasource
2 parents 97e5182 + 8056ffe commit d896f20

9 files changed

Lines changed: 267 additions & 98 deletions

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Remote Datasource
22

3-
#### Version 2.1
3+
#### Version 2.1.2
44

55
The Remote Datasource allows you to consume XML, JSON, CSV and TXT sources in Symphony. This extension aims to build upon the Dynamic XML datasource functionality provided in Symphony to allow better cache control, the automatic discovery of namespaces and more flexibility.
66

@@ -35,4 +35,14 @@ public static function prepareGateway(&$gateway) {}
3535
* the parsed xml string data returned by the Gateway by reference
3636
*/
3737
public function exposeData(&$data) {}
38+
39+
/**
40+
* This method is called when their is an http error
41+
* or when content type is unsupported
42+
*
43+
* @since Remote Datasource 2.0
44+
* @param array $info
45+
* info of the http request
46+
*/
47+
public function httpError(&$info) {}
3848
````

data-sources/datasource.remote.php

Lines changed: 105 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
class RemoteDatasource extends DataSource implements iDatasource
77
{
88

9+
private static $transformer = null;
910
private static $url_result = null;
10-
1111
private static $cacheable = null;
1212

1313
public static function getName()
@@ -71,6 +71,19 @@ public function exposeData(&$data)
7171

7272
}
7373

74+
/**
75+
* This method is called when their is an http error
76+
* or when content type is unsupported
77+
*
78+
* @since Remote Datasource 2.0
79+
* @param array $info
80+
* info of the http request
81+
*/
82+
public function httpError(&$info)
83+
{
84+
85+
}
86+
7487
/*-------------------------------------------------------------------------
7588
Utilities
7689
-------------------------------------------------------------------------*/
@@ -119,23 +132,7 @@ public static function isValidURL($url, $timeout = 6, $format = 'xml', $fetch_UR
119132
if (trim($url) == '') {
120133
return __('This is a required field');
121134
} elseif ($fetch_URL === true) {
122-
$gateway = new Gateway;
123-
$gateway->init($url);
124-
$gateway->setopt('TIMEOUT', $timeout);
125-
126-
// Set the approtiate Accept: headers depending on the format of the URL.
127-
if ($format == 'xml') {
128-
$gateway->setopt('HTTPHEADER', array('Accept: text/xml, */*'));
129-
} elseif ($format == 'json') {
130-
$gateway->setopt('HTTPHEADER', array('Accept: application/json, */*'));
131-
} elseif ($format == 'csv') {
132-
$gateway->setopt('HTTPHEADER', array('Accept: text/csv, */*'));
133-
}
134-
135-
self::prepareGateway($gateway);
136-
137-
$data = $gateway->exec();
138-
$info = $gateway->getInfoLast();
135+
list($data, $info) = self::fetch($url, $format, $timeout);
139136

140137
// 28 is CURLE_OPERATION_TIMEOUTED
141138
if (isset($info['curl_error']) && $info['curl_error'] == 28) {
@@ -226,7 +223,7 @@ public static function buildCacheID($settings)
226223
*/
227224
public static function buildCacheInformation(XMLElement $wrapper, Cacheable $cache, $cache_id)
228225
{
229-
$cachedData = $cache->check($cache_id);
226+
$cachedData = $cache->read($cache_id);
230227

231228
if (is_array($cachedData) && !empty($cachedData) && (time() < $cachedData['expiry'])) {
232229
$a = Widget::Anchor(__('Clear now'), SYMPHONY_URL . getCurrentPage() . 'clear_cache/');
@@ -256,7 +253,7 @@ public static function buildEditor(XMLElement $wrapper, array &$errors = array()
256253

257254
// If `clear_cache` is set, clear it..
258255
if (isset($cache_id) && in_array('clear_cache', Administration::instance()->Page->getContext())) {
259-
$cache->forceExpiry($cache_id);
256+
$cache->delete($cache_id);
260257
Administration::instance()->Page->pageAlert(
261258
__('Data source cache cleared at %s.', array(Widget::Time()->generate()))
262259
. '<a href="' . SYMPHONY_URL . '/blueprints/datasources/" accesskey="a">'
@@ -565,7 +562,8 @@ public static function prepare(array $settings, array $params, $template)
565562
$settings['namespaces'] = $namespaces;
566563
$cache = Symphony::ExtensionManager()->getCacheProvider('remotedatasource');
567564
$cache_id = self::buildCacheID($settings);
568-
$cache->write($cache_id, self::$url_result, $settings['cache']);
565+
$data = self::transformResult(self::$url_result, $settings['format']);
566+
$cache->write($cache_id, $data, $settings['cache']);
569567
}
570568

571569
return sprintf(
@@ -639,7 +637,7 @@ public function execute(array &$param_pool = null)
639637
// Check for an existing Cache for this Datasource
640638
$cache_id = self::buildCacheID($this);
641639
$cache = Symphony::ExtensionManager()->getCacheProvider('remotedatasource');
642-
$cachedData = $cache->check($cache_id);
640+
$cachedData = $cache->read($cache_id);
643641
$writeToCache = null;
644642
$isCacheValid = true;
645643
$creation = DateTimeObj::get('c');
@@ -650,34 +648,16 @@ public function execute(array &$param_pool = null)
650648
|| (time() - $cachedData['creation']) > ($this->dsParamCACHE * 60) // The cache is old.
651649
) {
652650
if (Mutex::acquire($cache_id, $this->dsParamTIMEOUT, TMP)) {
653-
$ch = new Gateway;
654-
$ch->init($this->dsParamURL);
655-
$ch->setopt('TIMEOUT', $this->dsParamTIMEOUT);
656-
657-
// Set the approtiate Accept: headers depending on the format of the URL.
658-
if ($this->dsParamFORMAT == 'xml') {
659-
$ch->setopt('HTTPHEADER', array('Accept: text/xml, */*'));
660-
} elseif ($this->dsParamFORMAT == 'json') {
661-
$ch->setopt('HTTPHEADER', array('Accept: application/json, */*'));
662-
} elseif ($this->dsParamFORMAT == 'csv') {
663-
$ch->setopt('HTTPHEADER', array('Accept: text/csv, */*'));
664-
}
665-
666-
self::prepareGateway($ch);
667-
668-
$data = $ch->exec();
669-
$info = $ch->getInfoLast();
670-
651+
list($data, $info) = self::fetch($this->dsParamURL, $this->dsParamFORMAT, $this->dsParamTIMEOUT);
671652
Mutex::release($cache_id, TMP);
672-
673-
$data = trim($data);
674653
$writeToCache = true;
675654

676655
// Handle any response that is not a 200, or the content type does not include XML, JSON, plain or text
677656
if ((int) $info['http_code'] != 200 || !preg_match('/(xml|json|csv|plain|text)/i', $info['content_type'])) {
678657
$writeToCache = false;
679658

680659
$result->setAttribute('valid', 'false');
660+
$this->httpError($info);
681661

682662
// 28 is CURLE_OPERATION_TIMEOUTED
683663
if ($info['curl_error'] == 28) {
@@ -697,76 +677,47 @@ public function execute(array &$param_pool = null)
697677
}
698678

699679
return $result;
700-
} else if (strlen($data) > 0) {
701680

702-
// Handle where there is `$data`
703-
704-
// If it's JSON, convert it to XML
705-
if ($this->dsParamFORMAT == 'json') {
706-
try {
707-
require_once TOOLKIT . '/class.json.php';
708-
$data = JSON::convertToXML($data);
709-
} catch (Exception $ex) {
710-
$writeToCache = false;
711-
$errors = array(
712-
array('message' => $ex->getMessage())
713-
);
714-
}
715-
} elseif ($this->dsParamFORMAT == 'csv') {
716-
try {
717-
require_once EXTENSIONS . '/remote_datasource/lib/class.csv.php';
718-
$data = CSV::convertToXML($data);
719-
} catch (Exception $ex) {
720-
$writeToCache = false;
721-
$errors = array(
722-
array('message' => $ex->getMessage())
723-
);
724-
}
725-
} elseif ($this->dsParamFORMAT == 'txt') {
726-
$txtElement = new XMLElement('entry');
727-
$txtElement->setValue(General::wrapInCDATA($data));
728-
$data = $txtElement->generate();
729-
$txtElement = null;
730-
}
731-
else if (!General::validateXML($data, $errors, false, new XsltProcess)) {
732-
// If the XML doesn't validate..
733-
$writeToCache = false;
681+
// Handle where there is `$data`
682+
} else if (strlen($data) > 0) {
683+
try {
684+
$data = self::transformResult($data, $this->dsParamFORMAT);
734685
}
735-
736686
// If the `$data` is invalid, return a result explaining why
737-
if ($writeToCache === false) {
687+
catch (TransformException $ex) {
688+
$writeToCache = false;
738689
$error = new XMLElement('errors');
739690
$error->setAttribute('valid', 'false');
740691

741692
$error->appendChild(new XMLElement('error', __('Data returned is invalid.')));
742693

743-
foreach ($errors as $e) {
694+
foreach ($ex->getErrors() as $e) {
744695
if (strlen(trim($e['message'])) == 0) {
745696
continue;
746697
}
747-
698+
748699
$error->appendChild(new XMLElement('item', General::sanitize($e['message'])));
749700
}
750701

751702
$result->appendChild($error);
752703

753704
return $result;
754705
}
706+
707+
// If `$data` is empty, set the `force_empty_result` to true.
755708
} elseif (strlen($data) == 0) {
756-
757-
// If `$data` is empty, set the `force_empty_result` to true.
758709
$this->_force_empty_result = true;
759710
}
711+
712+
// Failed to acquire a lock
760713
} else {
761-
762-
// Failed to acquire a lock
763714
$result->appendChild(
764715
new XMLElement('error', __('The %s class failed to acquire a lock.', array('<code>Mutex</code>')))
765716
);
766717
}
718+
719+
// The cache is good, use it!
767720
} else {
768-
769-
// The cache is good, use it!
770721
$data = trim($cachedData['data']);
771722
$creation = DateTimeObj::get('c', $cachedData['creation']);
772723
}
@@ -832,6 +783,75 @@ public function execute(array &$param_pool = null)
832783

833784
return $result;
834785
}
786+
787+
/**
788+
* Given a URL, Format and Timeout, this function will initalise
789+
* Symphony's Gateway class to retrieve the contents of the URL.
790+
*
791+
* @param string $url
792+
* @param string $format
793+
* @param integer $timeout
794+
* @return array
795+
*/
796+
public static function fetch($url, $format, $timeout)
797+
{
798+
$ch = new Gateway;
799+
$ch->init($url);
800+
$ch->setopt('TIMEOUT', $timeout);
801+
802+
// Set the approtiate Accept: headers depending on the format of the URL.
803+
if ($transformer = self::getTransformer($format)) {
804+
$accepts = $transformer->accepts();
805+
$ch->setopt('HTTPHEADER', array('Accept: ' . $accepts));
806+
}
807+
808+
self::prepareGateway($ch);
809+
810+
return array(
811+
trim($ch->exec()),
812+
$ch->getInfoLast()
813+
);
814+
}
815+
816+
/**
817+
* Given the result (a string), and a desired format, this
818+
* function will transform it to the desired format and return
819+
* it.
820+
* @param string $data
821+
* @param string $format
822+
* @return string
823+
*/
824+
public static function transformResult($data, $format)
825+
{
826+
if ($transformer = self::getTransformer($format)) {
827+
$data = $transformer->transform($data);
828+
} else {
829+
$data = '';
830+
}
831+
832+
return $data;
833+
}
834+
835+
/**
836+
* Given the format, this function will look for the file
837+
* and create a new transformer.
838+
*
839+
* @param string $format
840+
* @return Transformer
841+
*/
842+
public static function getTransformer($format)
843+
{
844+
$transformer = EXTENSIONS . '/remote_datasource/lib/class.' . strtolower($format) . '.php';
845+
846+
if (!isset(self::$transformer)) {
847+
if (file_exists($transformer)) {
848+
$classname = require_once $transformer;
849+
self::$transformer = new $classname;
850+
}
851+
}
852+
853+
return self::$transformer;
854+
}
835855
}
836856

837857
return 'RemoteDatasource';

extension.meta.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
</author>
1111
</authors>
1212
<releases>
13+
<release version="2.1.2" date="2014-12-17" min="2.4" max="2.6.x">
14+
- Fix the first cache of a result always resulting in an error
15+
- Abstract the transformers for better extensibility
16+
</release>
17+
<release version="2.1.1" date="2014-09-28" min="2.4">
18+
- Expose the CURL error via `httpError()`
19+
- Fix error with CSV importing
20+
</release>
1321
<release version="2.1.0" date="2014-06-25" min="2.4">
1422
- Add support for text format (a copy of the html response body)
1523
- Add some documentation

lib/class.csv.php

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
11
<?php
22

3-
class CSV
3+
require_once __DIR__ . '/interface.transformer.php';
4+
require_once __DIR__ . '/class.transformexception.php';
5+
6+
class CSVFormatter implements Transformer
47
{
8+
public function accepts()
9+
{
10+
return 'text/csv, */*';
11+
}
12+
13+
public function transform($data)
14+
{
15+
try {
16+
$data = self::convertToXML($data);
17+
} catch (Exception $ex) {
18+
throw new TransformException($ex->getMessage(), array(
19+
'message' => $ex->getMessages()
20+
));
21+
}
22+
23+
return $data;
24+
}
525

626
/**
727
* Given a CSV file, generate a resulting XML tree
@@ -50,19 +70,19 @@ public static function convertToXML($data)
5070
*/
5171
public static function addRow(DOMDocument $doc, DOMElement $root, $row, $headers)
5272
{
53-
foreach ($row as $column) {
54-
// Create <entry><header>value</header></entry>
55-
$entry = $doc->createElement('entry');
56-
57-
foreach ($headers as $i => $header) {
58-
$col = $doc->createElement($header);
59-
$col = $entry->appendChild($col);
73+
// Create <entry><header>value</header></entry>
74+
$entry = $doc->createElement('entry');
6075

61-
$value = $doc->createTextNode($row[$i]);
62-
$value = $col->appendChild($value);
63-
}
76+
foreach ($headers as $i => $header) {
77+
$col = $doc->createElement($header);
78+
$col = $entry->appendChild($col);
6479

65-
$root->appendChild($entry);
80+
$value = $doc->createTextNode($row[$i]);
81+
$value = $col->appendChild($value);
6682
}
83+
84+
$root->appendChild($entry);
6785
}
6886
}
87+
88+
return 'CSVFormatter';

0 commit comments

Comments
 (0)