I'm trying to get XML from an external link, parse and display the results.
The external XML link comes from a meta box in a post.
I can output/parse it on a page no problem, but I want to cache the results instead of loading it on every page hit. ( There are lots of requests and lots of xml files).
I though I would take advantage of WordPress's transient API ( if there is an alternative please let me know) to store the results in 24 hour intervals.
I get a node error :node no longer exists for code below. I do know that it is because the xml is being returned as an object and not a string.
What I don't know is how to make the object a string so I can store it using the transient API. Also due to this error I'm am not sure if I am using the transient API correctly.
ps. This example if brief, the actual version parses and outputs more data.
<?php
if ( is_single() ) {
global $wp_query;
$post = $wp_query->post;
$plugin_uri = get_post_meta($post->ID, 'xml_meta_value', true);
//check the db to see if it exists ( get_transient)
if (false === ($response_xml = get_transient('stats_from_xml_feed'))){
$request_url = "http://example.com/ $plugin_uri.xml";
$request_url = urlencode($request_url);
$response_xml = @simplexml_load_file($request_url);
//kill request if connection problem
if ($response_xml === FALSE){
exit ('could not connect');
} else {
// here we throw it into the WordPress temp DB using set_transient for 24 hours
set_transient('stats_from_xml_feed', $response_xml, 60*60*24);
//some output
$res = $response_xml;
$name = $res->name;
echo $name;
}
Scott answers:
Hi Bob,
You are on the right track. The trick here is that you want to store the raw xml and not a binary option. You can do this using the $response_xml->asXML() method (see http://www.php.net/manual/en/simplexmlelement.asxml.php). When you load this string again, run it back through simplexml_load_string and you'll be all set.
You may also want to look at http functions built in to wordpress. See the [[LINK href="http://core.trac.wordpress.org/browser/tags/3.1/wp-includes/http.php"]]http.php docs
here[[/LINK]].
I recently wrote a private plugin to fetch JSON data from a feed and also use the transient API. I abstracted it a bit for you here... it's untested but I think it should work.
<?php
/*
Plugin Name: Stereo XML Feed Plugin
Plugin URI: http://stereointeractive.com/
Description: Provides functions and template to query an XML feed.
Author: Scott Meves
Version: 1.0
Author URI: http://stereointeractive.com
*/
// return response body from a url as a string
function stereo_get_feed($url)
{
$data = false;
if ($url) {
$response = wp_remote_get($url, array('timeout' => 15));
if (!is_wp_error($response)) {
$data = wp_remote_retrieve_body($response);
}
}
return $data ? $data : false;
}
// check for cached text from a url, otherwise fetch it
// then convert it to an xml object
function stereo_get_xml_object($url)
{
$key = md5($url);
if (false == ($xml = get_transient('xml_'.$key))) {
$xml = stereo_get_feed($url);
if ($xml) {
set_transient('xml_'.$key, $xml, 60*60*8); // 8 hours
}
}
return $xml ? @simplexml_load_string($xml) : false;
}
Using those functions, your template code should be as simple as:
if ( is_single() ) {
global $wp_query;
$post = $wp_query->post;
$plugin_uri = get_post_meta($post->ID, 'xml_meta_value', true);
$xml = stereo_get_xml_object("http://example.com/ $plugin_uri.xml");
if ($xml) {
$name = $xml->name;
echo $name;
}
}
Bob comments:
Hi this is actually what I wanted to do, so thanks. Denzel Chia's solution worked all I had to do is throw it in an array but your plugin has some neat parameters.
When I tried using it though I get parse errors, I suspect this is due to a simplexml hiccup when lokking at this XML format.
The XML format is pretty much exactly like the api.wordpress.com one, for instance api.wordpress.org/plugins/info/1.0/video-sidebar-widgets.xml (just random one).
The parse error I get is
function.simplexml-load-string]: Entity: line 8: parser error : AttValue: " or ' expected
I thought maybe adding stripslashes to load would help, for instance
return $xml ? @simplexml_load_string(stripslashes($xml)) : false;
But that did nothing, I'm sure it something very simple I am overlooking.
Scott comments:
Bob, I tried with the URL you provided and it seemed to work OK for me. Can you provide another sample XML URL to try? An easy way to test the code is to create a test file in the root of your wordpress directory (test.php or something) and load it on your website. Here is the contents of a test.php file that works for me. It uses the same functions as above, but just stripped down to the basics. If it works, it should just show the name of the plugin on the page and nothing else.
<?php
// put this all inside a test.php file at the root of
// your wordpress directory and then load http://[yourwebsite.com]/test.php
require_once( dirname(__FILE__) . '/wp-load.php' );
wp();
// return response body from a url as a string
function stereo_get_feed($url)
{
$data = false;
if ($url) {
$response = wp_remote_get($url, array('timeout' => 15));
if (!is_wp_error($response)) {
$data = wp_remote_retrieve_body($response);
}
}
return $data ? $data : false;
}
// check for cached text from a url, otherwise fetch it
// then convert it to an xml object
function stereo_get_xml_object($url)
{
$key = md5($url);
if (false == ($xml = get_transient('xml_'.$key))) {
$xml = stereo_get_feed($url);
if ($xml) {
set_transient('xml_'.$key, $xml, 60*60*8); // 8 hours
}
}
return $xml ? @simplexml_load_string($xml) : false;
}
$xml = stereo_get_xml_object('http://api.wordpress.org/plugins/info/1.0/video-sidebar-widgets.xml');
if ($xml) {
$name = $xml->name;
echo $name;
}
?>
Bob comments:
Hmm, odd using this stripped down code works perfectly, I tested it on several XML urls. So far so good, I see it in the db as well.
Thanks Scott!
Scott comments:
I'm not sure if this is it, but I noticed we had the line:
$xml = stereo_get_xml_object("http://example.com/ $plugin_uri.xml");
(Notice that space in the address... make sure that is gone!)
Bob comments:
Ya might have been because looking back I'm not sure why, since it is now working.
Let me throw in the original output and parsing to see if anything gets borked, and if it's all good I will close this.
Denzel Chia answers:
Hi,
Just cast the object to array by adding (array) to the object.
example,
$response_array = array();
$response_array = (array) $response_xml;
Thanks.
Denzel
Denzel Chia comments:
Hi,
You should see what is being stored as the value of the transient in WordPress options table.
or print_r the retrieved transient value and see if the object structure has been altered.
Maybe the object structure is altered, that's why when you parse the retrieved transient value, there is error.
Thanks.
Denzel
Denzel Chia comments:
Hi,
This is a simple and good tutorial on transient API.
http://wpengineer.com/2148/simple-cache-with-the-wordpress-transient-api/
Bob comments:
Wow so simple and it works. Now I just have to understand how the transient API works :)
Question: This is now grabbing the whole XML and throwing it into the DB, if I only wanted to throw say the first 10 values into the DB , would I parse the array before set_transient , correct?
Bob comments:
Actually I spoke to soon...
When I did this the first load worked fine and I see is in the transient DB.
But when I reload the page I get:
unserialize() [function.unserialize]: Node no longer exists