1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Updater
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * Update class. It is used by JUpdater::update() to install an update. Use JUpdater::findUpdates() to find updates for
14 * an extension.
15 *
16 * @since 11.1
17 */
18 class JUpdate extends JObject
19 {
20 /**
21 * Update manifest `<name>` element
22 *
23 * @var string
24 * @since 11.1
25 */
26 protected $name;
27
28 /**
29 * Update manifest `<description>` element
30 *
31 * @var string
32 * @since 11.1
33 */
34 protected $description;
35
36 /**
37 * Update manifest `<element>` element
38 *
39 * @var string
40 * @since 11.1
41 */
42 protected $element;
43
44 /**
45 * Update manifest `<type>` element
46 *
47 * @var string
48 * @since 11.1
49 */
50 protected $type;
51
52 /**
53 * Update manifest `<version>` element
54 *
55 * @var string
56 * @since 11.1
57 */
58 protected $version;
59
60 /**
61 * Update manifest `<infourl>` element
62 *
63 * @var string
64 * @since 11.1
65 */
66 protected $infourl;
67
68 /**
69 * Update manifest `<client>` element
70 *
71 * @var string
72 * @since 11.1
73 */
74 protected $client;
75
76 /**
77 * Update manifest `<group>` element
78 *
79 * @var string
80 * @since 11.1
81 */
82 protected $group;
83
84 /**
85 * Update manifest `<downloads>` element
86 *
87 * @var string
88 * @since 11.1
89 */
90 protected $downloads;
91
92 /**
93 * Update manifest `<tags>` element
94 *
95 * @var string
96 * @since 11.1
97 */
98 protected $tags;
99
100 /**
101 * Update manifest `<maintainer>` element
102 *
103 * @var string
104 * @since 11.1
105 */
106 protected $maintainer;
107
108 /**
109 * Update manifest `<maintainerurl>` element
110 *
111 * @var string
112 * @since 11.1
113 */
114 protected $maintainerurl;
115
116 /**
117 * Update manifest `<category>` element
118 *
119 * @var string
120 * @since 11.1
121 */
122 protected $category;
123
124 /**
125 * Update manifest `<relationships>` element
126 *
127 * @var string
128 * @since 11.1
129 */
130 protected $relationships;
131
132 /**
133 * Update manifest `<targetplatform>` element
134 *
135 * @var string
136 * @since 11.1
137 */
138 protected $targetplatform;
139
140 /**
141 * Extra query for download URLs
142 *
143 * @var string
144 * @since 13.1
145 */
146 protected $extra_query;
147
148 /**
149 * Resource handle for the XML Parser
150 *
151 * @var resource
152 * @since 12.1
153 */
154 protected $xmlParser;
155
156 /**
157 * Element call stack
158 *
159 * @var array
160 * @since 12.1
161 */
162 protected $stack = array('base');
163
164 /**
165 * Unused state array
166 *
167 * @var array
168 * @since 12.1
169 */
170 protected $stateStore = array();
171
172 /**
173 * Object containing the current update data
174 *
175 * @var stdClass
176 * @since 12.1
177 */
178 protected $currentUpdate;
179
180 /**
181 * Object containing the latest update data
182 *
183 * @var stdClass
184 * @since 12.1
185 */
186 protected $latest;
187
188 /**
189 * The minimum stability required for updates to be taken into account. The possible values are:
190 * 0 dev Development snapshots, nightly builds, pre-release versions and so on
191 * 1 alpha Alpha versions (work in progress, things are likely to be broken)
192 * 2 beta Beta versions (major functionality in place, show-stopper bugs are likely to be present)
193 * 3 rc Release Candidate versions (almost stable, minor bugs might be present)
194 * 4 stable Stable versions (production quality code)
195 *
196 * @var int
197 * @since 14.1
198 *
199 * @see JUpdater
200 */
201 protected $minimum_stability = JUpdater::STABILITY_STABLE;
202
203 /**
204 * Gets the reference to the current direct parent
205 *
206 * @return object
207 *
208 * @since 11.1
209 */
210 protected function _getStackLocation()
211 {
212 return implode('->', $this->stack);
213 }
214
215 /**
216 * Get the last position in stack count
217 *
218 * @return string
219 *
220 * @since 11.1
221 */
222 protected function _getLastTag()
223 {
224 return $this->stack[count($this->stack) - 1];
225 }
226
227 /**
228 * XML Start Element callback
229 *
230 * @param object $parser Parser object
231 * @param string $name Name of the tag found
232 * @param array $attrs Attributes of the tag
233 *
234 * @return void
235 *
236 * @note This is public because it is called externally
237 * @since 11.1
238 */
239 public function _startElement($parser, $name, $attrs = array())
240 {
241 $this->stack[] = $name;
242 $tag = $this->_getStackLocation();
243
244 // Reset the data
245 if (isset($this->$tag))
246 {
247 $this->$tag->_data = '';
248 }
249
250 switch ($name)
251 {
252 // This is a new update; create a current update
253 case 'UPDATE':
254 $this->currentUpdate = new stdClass;
255 break;
256
257 // Don't do anything
258 case 'UPDATES':
259 break;
260
261 // For everything else there's...the default!
262 default:
263 $name = strtolower($name);
264
265 if (!isset($this->currentUpdate->$name))
266 {
267 $this->currentUpdate->$name = new stdClass;
268 }
269
270 $this->currentUpdate->$name->_data = '';
271
272 foreach ($attrs as $key => $data)
273 {
274 $key = strtolower($key);
275 $this->currentUpdate->$name->$key = $data;
276 }
277 break;
278 }
279 }
280
281 /**
282 * Callback for closing the element
283 *
284 * @param object $parser Parser object
285 * @param string $name Name of element that was closed
286 *
287 * @return void
288 *
289 * @note This is public because it is called externally
290 * @since 11.1
291 */
292 public function _endElement($parser, $name)
293 {
294 array_pop($this->stack);
295
296 switch ($name)
297 {
298 // Closing update, find the latest version and check
299 case 'UPDATE':
300 $product = strtolower(JFilterInput::getInstance()->clean(JVersion::PRODUCT, 'cmd'));
301
302 // Support for the min_dev_level and max_dev_level attributes is deprecated, a regexp should be used instead
303 if (isset($this->currentUpdate->targetplatform->min_dev_level) || isset($this->currentUpdate->targetplatform->max_dev_level))
304 {
305 JLog::add(
306 'Support for the min_dev_level and max_dev_level attributes of an update\'s <targetplatform> tag is deprecated and'
307 . ' will be removed in 4.0. The full version should be specified in the version attribute and may optionally be a regexp.',
308 JLog::WARNING,
309 'deprecated'
310 );
311 }
312
313 /*
314 * Check that the product matches and that the version matches (optionally a regexp)
315 *
316 * Check for optional min_dev_level and max_dev_level attributes to further specify targetplatform (e.g., 3.0.1)
317 */
318 if (isset($this->currentUpdate->targetplatform->name)
319 && $product == $this->currentUpdate->targetplatform->name
320 && preg_match('/^' . $this->currentUpdate->targetplatform->version . '/', $this->get('jversion.full', JVERSION))
321 && ((!isset($this->currentUpdate->targetplatform->min_dev_level))
322 || $this->get('jversion.dev_level', JVersion::DEV_LEVEL) >= $this->currentUpdate->targetplatform->min_dev_level)
323 && ((!isset($this->currentUpdate->targetplatform->max_dev_level))
324 || $this->get('jversion.dev_level', JVersion::DEV_LEVEL) <= $this->currentUpdate->targetplatform->max_dev_level))
325 {
326 $phpMatch = false;
327
328 // Check if PHP version supported via <php_minimum> tag, assume true if tag isn't present
329 if (!isset($this->currentUpdate->php_minimum) || version_compare(PHP_VERSION, $this->currentUpdate->php_minimum->_data, '>='))
330 {
331 $phpMatch = true;
332 }
333
334 $dbMatch = false;
335
336 // Check if DB & version is supported via <supported_databases> tag, assume supported if tag isn't present
337 if (isset($this->currentUpdate->supported_databases))
338 {
339 $db = JFactory::getDbo();
340 $dbType = strtolower($db->getServerType());
341 $dbVersion = $db->getVersion();
342 $supportedDbs = $this->currentUpdate->supported_databases;
343
344 // Do we have a entry for the database?
345 if (isset($supportedDbs->$dbType))
346 {
347 $minumumVersion = $supportedDbs->$dbType;
348 $dbMatch = version_compare($dbVersion, $minumumVersion, '>=');
349 }
350 }
351 else
352 {
353 // Set to true if the <supported_databases> tag is not set
354 $dbMatch = true;
355 }
356
357 // Check minimum stability
358 $stabilityMatch = true;
359
360 if (isset($this->currentUpdate->stability) && ($this->currentUpdate->stability < $this->minimum_stability))
361 {
362 $stabilityMatch = false;
363 }
364
365 if ($phpMatch && $stabilityMatch && $dbMatch)
366 {
367 if (isset($this->latest))
368 {
369 if (version_compare($this->currentUpdate->version->_data, $this->latest->version->_data, '>') == 1)
370 {
371 $this->latest = $this->currentUpdate;
372 }
373 }
374 else
375 {
376 $this->latest = $this->currentUpdate;
377 }
378 }
379 }
380 break;
381 case 'UPDATES':
382 // If the latest item is set then we transfer it to where we want to
383 if (isset($this->latest))
384 {
385 foreach (get_object_vars($this->latest) as $key => $val)
386 {
387 $this->$key = $val;
388 }
389
390 unset($this->latest);
391 unset($this->currentUpdate);
392 }
393 elseif (isset($this->currentUpdate))
394 {
395 // The update might be for an older version of j!
396 unset($this->currentUpdate);
397 }
398 break;
399 }
400 }
401
402 /**
403 * Character Parser Function
404 *
405 * @param object $parser Parser object.
406 * @param object $data The data.
407 *
408 * @return void
409 *
410 * @note This is public because its called externally.
411 * @since 11.1
412 */
413 public function _characterData($parser, $data)
414 {
415 $tag = $this->_getLastTag();
416
417 // Throw the data for this item together
418 $tag = strtolower($tag);
419
420 if ($tag == 'tag')
421 {
422 $this->currentUpdate->stability = $this->stabilityTagToInteger((string) $data);
423
424 return;
425 }
426
427 if (isset($this->currentUpdate->$tag))
428 {
429 $this->currentUpdate->$tag->_data .= $data;
430 }
431 }
432
433 /**
434 * Loads an XML file from a URL.
435 *
436 * @param string $url The URL.
437 * @param int $minimum_stability The minimum stability required for updating the extension {@see JUpdater}
438 *
439 * @return boolean True on success
440 *
441 * @since 11.1
442 */
443 public function loadFromXml($url, $minimum_stability = JUpdater::STABILITY_STABLE)
444 {
445 $http = JHttpFactory::getHttp();
446
447 try
448 {
449 $response = $http->get($url);
450 }
451 catch (RuntimeException $e)
452 {
453 $response = null;
454 }
455
456 if ($response === null || $response->code !== 200)
457 {
458 // TODO: Add a 'mark bad' setting here somehow
459 JLog::add(JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_OPEN_URL', $url), JLog::WARNING, 'jerror');
460
461 return false;
462 }
463
464 $this->minimum_stability = $minimum_stability;
465
466 $this->xmlParser = xml_parser_create('');
467 xml_set_object($this->xmlParser, $this);
468 xml_set_element_handler($this->xmlParser, '_startElement', '_endElement');
469 xml_set_character_data_handler($this->xmlParser, '_characterData');
470
471 if (!xml_parse($this->xmlParser, $response->body))
472 {
473 JLog::add(
474 sprintf(
475 'XML error: %s at line %d', xml_error_string(xml_get_error_code($this->xmlParser)),
476 xml_get_current_line_number($this->xmlParser)
477 ),
478 JLog::WARNING, 'updater'
479 );
480
481 return false;
482 }
483
484 xml_parser_free($this->xmlParser);
485
486 return true;
487 }
488
489 /**
490 * Converts a tag to numeric stability representation. If the tag doesn't represent a known stability level (one of
491 * dev, alpha, beta, rc, stable) it is ignored.
492 *
493 * @param string $tag The tag string, e.g. dev, alpha, beta, rc, stable
494 *
495 * @return integer
496 *
497 * @since 3.4
498 */
499 protected function stabilityTagToInteger($tag)
500 {
501 $constant = 'JUpdater::STABILITY_' . strtoupper($tag);
502
503 if (defined($constant))
504 {
505 return constant($constant);
506 }
507
508 return JUpdater::STABILITY_STABLE;
509 }
510 }
511