1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Unicodeveloper\LaravelFeeder; |
4
|
|
|
|
5
|
|
|
use SimpleXMLElement; |
6
|
|
|
use Unicodeveloper\LaravelFeeder\Exceptions\FeedException; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* RSS for PHP - small and easy-to-use library for consuming an RSS Feed |
10
|
|
|
* |
11
|
|
|
* @author David Grudl |
12
|
|
|
* @copyright Copyright (c) 2008 David Grudl |
13
|
|
|
* @license New BSD License |
14
|
|
|
* @link http://phpfashion.com/ |
15
|
|
|
* @version 1.1 |
16
|
|
|
*/ |
17
|
|
|
class Feed |
18
|
|
|
{ |
19
|
|
|
/** @var int */ |
20
|
|
|
public static $cacheExpire = 86400; // 1 day |
21
|
|
|
|
22
|
|
|
/** @var string */ |
23
|
|
|
public static $cacheDir; |
24
|
|
|
|
25
|
|
|
/** @var SimpleXMLElement */ |
26
|
|
|
protected $xml; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Loads RSS channel. |
30
|
|
|
* @param string RSS feed URL |
31
|
|
|
* @param string optional user name |
32
|
|
|
* @param string optional password |
33
|
|
|
* @return Feed |
34
|
|
|
* @throws FeedException |
35
|
|
|
*/ |
36
|
|
|
public static function loadRss($url, $user = NULL, $pass = NULL) |
37
|
|
|
{ |
38
|
|
|
$xml = new SimpleXMLElement(self::httpRequest($url, $user, $pass), LIBXML_NOWARNING | LIBXML_NOERROR); |
39
|
|
|
if (!$xml->channel) { |
40
|
|
|
throw new FeedException('Invalid channel.'); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
self::adjustNamespaces($xml->channel); |
44
|
|
|
|
45
|
|
|
foreach ($xml->channel->item as $item) { |
46
|
|
|
// converts namespaces to dotted tags |
47
|
|
|
self::adjustNamespaces($item); |
48
|
|
|
|
49
|
|
|
// generate 'timestamp' tag |
50
|
|
|
if (isset($item->{'dc:date'})) { |
51
|
|
|
$item->timestamp = strtotime($item->{'dc:date'}); |
52
|
|
|
} elseif (isset($item->pubDate)) { |
53
|
|
|
$item->timestamp = strtotime($item->pubDate); |
54
|
|
|
} |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
$feed = new self; |
58
|
|
|
$feed->xml = $xml->channel; |
|
|
|
|
59
|
|
|
return $feed; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Loads Atom channel. |
64
|
|
|
* @param string Atom feed URL |
65
|
|
|
* @param string optional user name |
66
|
|
|
* @param string optional password |
67
|
|
|
* @return Feed |
68
|
|
|
* @throws FeedException |
69
|
|
|
*/ |
70
|
|
|
public static function loadAtom($url, $user = NULL, $pass = NULL) |
71
|
|
|
{ |
72
|
|
|
$xml = new SimpleXMLElement(self::httpRequest($url, $user, $pass), LIBXML_NOWARNING | LIBXML_NOERROR); |
73
|
|
|
if (!in_array('http://www.w3.org/2005/Atom', $xml->getDocNamespaces(), TRUE)) { |
74
|
|
|
throw new FeedException('Invalid channel.'); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
// generate 'timestamp' tag |
78
|
|
|
foreach ($xml->entry as $entry) { |
79
|
|
|
$entry->timestamp = strtotime($entry->updated); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
$feed = new self; |
83
|
|
|
$feed->xml = $xml; |
84
|
|
|
return $feed; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Returns property value. Do not call directly. |
89
|
|
|
* @param string tag name |
90
|
|
|
* @return SimpleXMLElement |
91
|
|
|
*/ |
92
|
|
|
public function __get($name) |
93
|
|
|
{ |
94
|
|
|
return $this->xml->{$name}; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Sets value of a property. Do not call directly. |
99
|
|
|
* @param string property name |
100
|
|
|
* @param mixed property value |
101
|
|
|
* @return void |
102
|
|
|
*/ |
103
|
|
|
public function __set($name, $value) |
|
|
|
|
104
|
|
|
{ |
105
|
|
|
throw new Exception("Cannot assign to a read-only property '$name'."); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Converts a SimpleXMLElement into an array. |
110
|
|
|
* @param SimpleXMLElement |
111
|
|
|
* @return array |
112
|
|
|
*/ |
113
|
|
|
public function toArray(SimpleXMLElement $xml = NULL) |
114
|
|
|
{ |
115
|
|
|
if ($xml === NULL) { |
116
|
|
|
$xml = $this->xml; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
if (!$xml->children()) { |
120
|
|
|
return (string) $xml; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$arr = array(); |
124
|
|
|
foreach ($xml->children() as $tag => $child) { |
125
|
|
|
if (count($xml->$tag) === 1) { |
126
|
|
|
$arr[$tag] = $this->toArray($child); |
127
|
|
|
} else { |
128
|
|
|
$arr[$tag][] = $this->toArray($child); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return $arr; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Process HTTP request. |
137
|
|
|
* @param string URL |
138
|
|
|
* @param string user name |
139
|
|
|
* @param string password |
140
|
|
|
* @return string |
141
|
|
|
* @throws FeedException |
142
|
|
|
*/ |
143
|
|
|
private static function httpRequest($url, $user, $pass) |
144
|
|
|
{ |
145
|
|
|
if (self::$cacheDir) { |
146
|
|
|
$cacheFile = self::$cacheDir . '/feed.' . md5($url) . '.xml'; |
147
|
|
|
if (@filemtime($cacheFile) + self::$cacheExpire > time()) { |
148
|
|
|
return file_get_contents($cacheFile); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
if (extension_loaded('curl')) { |
153
|
|
|
$curl = curl_init(); |
154
|
|
|
curl_setopt($curl, CURLOPT_URL, $url); |
155
|
|
|
if ($user !== NULL || $pass !== NULL) { |
156
|
|
|
curl_setopt($curl, CURLOPT_USERPWD, "$user:$pass"); |
157
|
|
|
} |
158
|
|
|
curl_setopt($curl, CURLOPT_HEADER, FALSE); |
159
|
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, 20); |
160
|
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); // no echo, just return result |
161
|
|
|
if (!ini_get('open_basedir')) { |
162
|
|
|
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE); // sometime is useful :) |
163
|
|
|
} |
164
|
|
|
$result = curl_exec($curl); |
165
|
|
|
$ok = curl_errno($curl) === 0 && curl_getinfo($curl, CURLINFO_HTTP_CODE) === 200; |
166
|
|
|
|
167
|
|
|
} elseif ($user === NULL && $pass === NULL) { |
168
|
|
|
$result = file_get_contents($url); |
169
|
|
|
$ok = is_string($result); |
170
|
|
|
|
171
|
|
|
} else { |
172
|
|
|
throw new FeedException('PHP extension CURL is not loaded.'); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
if (!$ok) { |
176
|
|
|
if (isset($cacheFile)) { |
177
|
|
|
$result = @file_get_contents($cacheFile); |
178
|
|
|
if (is_string($result)) { |
179
|
|
|
return $result; |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
throw new FeedException('Cannot load channel.'); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
if (isset($cacheFile)) { |
186
|
|
|
file_put_contents($cacheFile, $result); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return $result; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Generates better accessible namespaced tags. |
194
|
|
|
* @param SimpleXMLElement |
195
|
|
|
* @return void |
196
|
|
|
*/ |
197
|
|
|
private static function adjustNamespaces($el) |
198
|
|
|
{ |
199
|
|
|
foreach ($el->getNamespaces(TRUE) as $prefix => $ns) { |
200
|
|
|
$children = $el->children($ns); |
201
|
|
|
foreach ($children as $tag => $content) { |
202
|
|
|
$el->{$prefix . ':' . $tag} = $content; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.