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 jimport('joomla.updater.updateadapter');
13
14 /**
15 * Extension class for updater
16 *
17 * @since 11.1
18 */
19 class JUpdaterExtension extends JUpdateAdapter
20 {
21 /**
22 * Start element parser callback.
23 *
24 * @param object $parser The parser object.
25 * @param string $name The name of the element.
26 * @param array $attrs The attributes of the element.
27 *
28 * @return void
29 *
30 * @since 11.1
31 */
32 protected function _startElement($parser, $name, $attrs = array())
33 {
34 $this->stack[] = $name;
35 $tag = $this->_getStackLocation();
36
37 // Reset the data
38 if (isset($this->$tag))
39 {
40 $this->$tag->_data = '';
41 }
42
43 switch ($name)
44 {
45 case 'UPDATE':
46 $this->currentUpdate = JTable::getInstance('update');
47 $this->currentUpdate->update_site_id = $this->updateSiteId;
48 $this->currentUpdate->detailsurl = $this->_url;
49 $this->currentUpdate->folder = '';
50 $this->currentUpdate->client_id = 1;
51 break;
52
53 // Don't do anything
54 case 'UPDATES':
55 break;
56
57 default:
58 if (in_array($name, $this->updatecols))
59 {
60 $name = strtolower($name);
61 $this->currentUpdate->$name = '';
62 }
63
64 if ($name == 'TARGETPLATFORM')
65 {
66 $this->currentUpdate->targetplatform = $attrs;
67 }
68
69 if ($name == 'PHP_MINIMUM')
70 {
71 $this->currentUpdate->php_minimum = '';
72 }
73
74 if ($name == 'SUPPORTED_DATABASES')
75 {
76 $this->currentUpdate->supported_databases = $attrs;
77 }
78 break;
79 }
80 }
81
82 /**
83 * Character Parser Function
84 *
85 * @param object $parser Parser object.
86 * @param object $name The name of the element.
87 *
88 * @return void
89 *
90 * @since 11.1
91 */
92 protected function _endElement($parser, $name)
93 {
94 array_pop($this->stack);
95
96 // @todo remove code: echo 'Closing: '. $name .'<br />';
97 switch ($name)
98 {
99 case 'UPDATE':
100 // Lower case and remove the exclamation mark
101 $product = strtolower(JFilterInput::getInstance()->clean(JVersion::PRODUCT, 'cmd'));
102
103 // Support for the min_dev_level and max_dev_level attributes is deprecated, a regexp should be used instead
104 if (isset($this->currentUpdate->targetplatform->min_dev_level) || isset($this->currentUpdate->targetplatform->max_dev_level))
105 {
106 JLog::add(
107 'Support for the min_dev_level and max_dev_level attributes of an update\'s <targetplatform> tag is deprecated and'
108 . ' will be removed in 4.0. The full version should be specified in the version attribute and may optionally be a regexp.',
109 JLog::WARNING,
110 'deprecated'
111 );
112 }
113
114 /*
115 * Check that the product matches and that the version matches (optionally a regexp)
116 *
117 * Check for optional min_dev_level and max_dev_level attributes to further specify targetplatform (e.g., 3.0.1)
118 */
119 if ($product == $this->currentUpdate->targetplatform['NAME']
120 && preg_match('/^' . $this->currentUpdate->targetplatform['VERSION'] . '/', JVERSION)
121 && ((!isset($this->currentUpdate->targetplatform->min_dev_level)) || JVersion::DEV_LEVEL >= $this->currentUpdate->targetplatform->min_dev_level)
122 && ((!isset($this->currentUpdate->targetplatform->max_dev_level)) || JVersion::DEV_LEVEL <= $this->currentUpdate->targetplatform->max_dev_level))
123 {
124 // Check if PHP version supported via <php_minimum> tag, assume true if tag isn't present
125 if (!isset($this->currentUpdate->php_minimum) || version_compare(PHP_VERSION, $this->currentUpdate->php_minimum, '>='))
126 {
127 $phpMatch = true;
128 }
129 else
130 {
131 // Notify the user of the potential update
132 $msg = JText::sprintf(
133 'JLIB_INSTALLER_AVAILABLE_UPDATE_PHP_VERSION',
134 $this->currentUpdate->name,
135 $this->currentUpdate->version,
136 $this->currentUpdate->php_minimum,
137 PHP_VERSION
138 );
139
140 JFactory::getApplication()->enqueueMessage($msg, 'warning');
141
142 $phpMatch = false;
143 }
144
145 $dbMatch = false;
146
147 // Check if DB & version is supported via <supported_databases> tag, assume supported if tag isn't present
148 if (isset($this->currentUpdate->supported_databases))
149 {
150 $db = JFactory::getDbo();
151 $dbType = strtoupper($db->getServerType());
152 $dbVersion = $db->getVersion();
153 $supportedDbs = $this->currentUpdate->supported_databases;
154
155 // Do we have a entry for the database?
156 if (array_key_exists($dbType, $supportedDbs))
157 {
158 $minumumVersion = $supportedDbs[$dbType];
159 $dbMatch = version_compare($dbVersion, $minumumVersion, '>=');
160
161 if (!$dbMatch)
162 {
163 // Notify the user of the potential update
164 $dbMsg = JText::sprintf(
165 'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_MINIMUM',
166 $this->currentUpdate->name,
167 $this->currentUpdate->version,
168 JText::_($db->name),
169 $dbVersion,
170 $minumumVersion
171 );
172
173 JFactory::getApplication()->enqueueMessage($dbMsg, 'warning');
174 }
175 }
176 else
177 {
178 // Notify the user of the potential update
179 $dbMsg = JText::sprintf(
180 'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_TYPE',
181 $this->currentUpdate->name,
182 $this->currentUpdate->version,
183 JText::_($db->name)
184 );
185
186 JFactory::getApplication()->enqueueMessage($dbMsg, 'warning');
187 }
188 }
189 else
190 {
191 // Set to true if the <supported_databases> tag is not set
192 $dbMatch = true;
193 }
194
195 // Check minimum stability
196 $stabilityMatch = true;
197
198 if (isset($this->currentUpdate->stability) && ($this->currentUpdate->stability < $this->minimum_stability))
199 {
200 $stabilityMatch = false;
201 }
202
203 // Some properties aren't valid fields in the update table so unset them to prevent J! from trying to store them
204 unset($this->currentUpdate->targetplatform);
205
206 if (isset($this->currentUpdate->php_minimum))
207 {
208 unset($this->currentUpdate->php_minimum);
209 }
210
211 if (isset($this->currentUpdate->supported_databases))
212 {
213 unset($this->currentUpdate->supported_databases);
214 }
215
216 if (isset($this->currentUpdate->stability))
217 {
218 unset($this->currentUpdate->stability);
219 }
220
221 // If the PHP version and minimum stability checks pass, consider this version as a possible update
222 if ($phpMatch && $stabilityMatch && $dbMatch)
223 {
224 if (isset($this->latest))
225 {
226 // We already have a possible update. Check the version.
227 if (version_compare($this->currentUpdate->version, $this->latest->version, '>') == 1)
228 {
229 $this->latest = $this->currentUpdate;
230 }
231 }
232 else
233 {
234 // We don't have any possible updates yet, assume this is an available update.
235 $this->latest = $this->currentUpdate;
236 }
237 }
238 }
239 break;
240
241 case 'UPDATES':
242 // :D
243 break;
244 }
245 }
246
247 /**
248 * Character Parser Function
249 *
250 * @param object $parser Parser object.
251 * @param object $data The data.
252 *
253 * @return void
254 *
255 * @note This is public because its called externally.
256 * @since 11.1
257 */
258 protected function _characterData($parser, $data)
259 {
260 $tag = $this->_getLastTag();
261
262 if (in_array($tag, $this->updatecols))
263 {
264 $tag = strtolower($tag);
265 $this->currentUpdate->$tag .= $data;
266 }
267
268 if ($tag == 'PHP_MINIMUM')
269 {
270 $this->currentUpdate->php_minimum = $data;
271 }
272
273 if ($tag == 'TAG')
274 {
275 $this->currentUpdate->stability = $this->stabilityTagToInteger((string) $data);
276 }
277 }
278
279 /**
280 * Finds an update.
281 *
282 * @param array $options Update options.
283 *
284 * @return array Array containing the array of update sites and array of updates
285 *
286 * @since 11.1
287 */
288 public function findUpdate($options)
289 {
290 $response = $this->getUpdateSiteResponse($options);
291
292 if ($response === false)
293 {
294 return false;
295 }
296
297 if (array_key_exists('minimum_stability', $options))
298 {
299 $this->minimum_stability = $options['minimum_stability'];
300 }
301
302 $this->xmlParser = xml_parser_create('');
303 xml_set_object($this->xmlParser, $this);
304 xml_set_element_handler($this->xmlParser, '_startElement', '_endElement');
305 xml_set_character_data_handler($this->xmlParser, '_characterData');
306
307 if (!xml_parse($this->xmlParser, $response->body))
308 {
309 // If the URL is missing the .xml extension, try appending it and retry loading the update
310 if (!$this->appendExtension && (substr($this->_url, -4) != '.xml'))
311 {
312 $options['append_extension'] = true;
313
314 return $this->findUpdate($options);
315 }
316
317 JLog::add('Error parsing url: ' . $this->_url, JLog::WARNING, 'updater');
318
319 $app = JFactory::getApplication();
320 $app->enqueueMessage(JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_PARSE_URL', $this->_url), 'warning');
321
322 return false;
323 }
324
325 xml_parser_free($this->xmlParser);
326
327 if (isset($this->latest))
328 {
329 if (isset($this->latest->client) && strlen($this->latest->client))
330 {
331 if (is_numeric($this->latest->client))
332 {
333 $byName = false;
334
335 // <client> has to be 'administrator' or 'site', numeric values are deprecated. See https://docs.joomla.org/Special:MyLanguage/Design_of_JUpdate
336 JLog::add(
337 'Using numeric values for <client> in the updater xml is deprecated. Use \'administrator\' or \'site\' instead.',
338 JLog::WARNING, 'deprecated'
339 );
340 }
341 else
342 {
343 $byName = true;
344 }
345
346 $this->latest->client_id = JApplicationHelper::getClientInfo($this->latest->client, $byName)->id;
347 unset($this->latest->client);
348 }
349
350 $updates = array($this->latest);
351 }
352 else
353 {
354 $updates = array();
355 }
356
357 return array('update_sites' => array(), 'updates' => $updates);
358 }
359
360 /**
361 * Converts a tag to numeric stability representation. If the tag doesn't represent a known stability level (one of
362 * dev, alpha, beta, rc, stable) it is ignored.
363 *
364 * @param string $tag The tag string, e.g. dev, alpha, beta, rc, stable
365 *
366 * @return integer
367 *
368 * @since 3.4
369 */
370 protected function stabilityTagToInteger($tag)
371 {
372 $constant = 'JUpdater::STABILITY_' . strtoupper($tag);
373
374 if (defined($constant))
375 {
376 return constant($constant);
377 }
378
379 return JUpdater::STABILITY_STABLE;
380 }
381 }
382