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.base.adapterinstance');
13
14 /**
15 * UpdateAdapter class.
16 *
17 * @since 11.1
18 */
19 abstract class JUpdateAdapter extends JAdapterInstance
20 {
21 /**
22 * Resource handle for the XML Parser
23 *
24 * @var resource
25 * @since 12.1
26 */
27 protected $xmlParser;
28
29 /**
30 * Element call stack
31 *
32 * @var array
33 * @since 12.1
34 */
35 protected $stack = array('base');
36
37 /**
38 * ID of update site
39 *
40 * @var string
41 * @since 12.1
42 */
43 protected $updateSiteId = 0;
44
45 /**
46 * Columns in the extensions table to be updated
47 *
48 * @var array
49 * @since 12.1
50 */
51 protected $updatecols = array('NAME', 'ELEMENT', 'TYPE', 'FOLDER', 'CLIENT', 'VERSION', 'DESCRIPTION', 'INFOURL', 'EXTRA_QUERY');
52
53 /**
54 * Should we try appending a .xml extension to the update site's URL?
55 *
56 * @var bool
57 */
58 protected $appendExtension = false;
59
60 /**
61 * The name of the update site (used in logging)
62 *
63 * @var string
64 */
65 protected $updateSiteName = '';
66
67 /**
68 * The update site URL from which we will get the update information
69 *
70 * @var string
71 */
72 protected $_url = '';
73
74 /**
75 * The minimum stability required for updates to be taken into account. The possible values are:
76 * 0 dev Development snapshots, nightly builds, pre-release versions and so on
77 * 1 alpha Alpha versions (work in progress, things are likely to be broken)
78 * 2 beta Beta versions (major functionality in place, show-stopper bugs are likely to be present)
79 * 3 rc Release Candidate versions (almost stable, minor bugs might be present)
80 * 4 stable Stable versions (production quality code)
81 *
82 * @var int
83 * @since 14.1
84 *
85 * @see JUpdater
86 */
87 protected $minimum_stability = JUpdater::STABILITY_STABLE;
88
89 /**
90 * Gets the reference to the current direct parent
91 *
92 * @return object
93 *
94 * @since 11.1
95 */
96 protected function _getStackLocation()
97 {
98 return implode('->', $this->stack);
99 }
100
101 /**
102 * Gets the reference to the last tag
103 *
104 * @return object
105 *
106 * @since 11.1
107 */
108 protected function _getLastTag()
109 {
110 return $this->stack[count($this->stack) - 1];
111 }
112
113 /**
114 * Finds an update
115 *
116 * @param array $options Options to use: update_site_id: the unique ID of the update site to look at
117 *
118 * @return array Update_sites and updates discovered
119 *
120 * @since 11.1
121 */
122 abstract public function findUpdate($options);
123
124 /**
125 * Toggles the enabled status of an update site. Update sites are disabled before getting the update information
126 * from their URL and enabled afterwards. If the URL fetch fails with a PHP fatal error (e.g. timeout) the faulty
127 * update site will remain disabled the next time we attempt to load the update information.
128 *
129 * @param int $update_site_id The numeric ID of the update site to enable/disable
130 * @param bool $enabled Enable the site when true, disable it when false
131 *
132 * @return void
133 */
134 protected function toggleUpdateSite($update_site_id, $enabled = true)
135 {
136 $update_site_id = (int) $update_site_id;
137 $enabled = (bool) $enabled;
138
139 if (empty($update_site_id))
140 {
141 return;
142 }
143
144 $db = $this->parent->getDbo();
145 $query = $db->getQuery(true)
146 ->update($db->qn('#__update_sites'))
147 ->set($db->qn('enabled') . ' = ' . $db->q($enabled ? 1 : 0))
148 ->where($db->qn('update_site_id') . ' = ' . $db->q($update_site_id));
149 $db->setQuery($query);
150
151 try
152 {
153 $db->execute();
154 }
155 catch (RuntimeException $e)
156 {
157 // Do nothing
158 }
159 }
160
161 /**
162 * Get the name of an update site. This is used in logging.
163 *
164 * @param int $updateSiteId The numeric ID of the update site
165 *
166 * @return string The name of the update site or an empty string if it's not found
167 */
168 protected function getUpdateSiteName($updateSiteId)
169 {
170 $updateSiteId = (int) $updateSiteId;
171
172 if (empty($updateSiteId))
173 {
174 return '';
175 }
176
177 $db = $this->parent->getDbo();
178 $query = $db->getQuery(true)
179 ->select($db->qn('name'))
180 ->from($db->qn('#__update_sites'))
181 ->where($db->qn('update_site_id') . ' = ' . $db->q($updateSiteId));
182 $db->setQuery($query);
183
184 $name = '';
185
186 try
187 {
188 $name = $db->loadResult();
189 }
190 catch (RuntimeException $e)
191 {
192 // Do nothing
193 }
194
195 return $name;
196 }
197
198 /**
199 * Try to get the raw HTTP response from the update site, hopefully containing the update XML.
200 *
201 * @param array $options The update options, see findUpdate() in children classes
202 *
203 * @return bool|JHttpResponse False if we can't connect to the site, JHttpResponse otherwise
204 *
205 * @throws Exception
206 */
207 protected function getUpdateSiteResponse($options = array())
208 {
209 $url = trim($options['location']);
210 $this->_url = &$url;
211 $this->updateSiteId = $options['update_site_id'];
212
213 if (!isset($options['update_site_name']))
214 {
215 $options['update_site_name'] = $this->getUpdateSiteName($this->updateSiteId);
216 }
217 $this->updateSiteName = $options['update_site_name'];
218
219 $this->appendExtension = false;
220
221 if (array_key_exists('append_extension', $options))
222 {
223 $this->appendExtension = $options['append_extension'];
224 }
225
226 if ($this->appendExtension && (substr($url, -4) != '.xml'))
227 {
228 if (substr($url, -1) != '/')
229 {
230 $url .= '/';
231 }
232
233 $url .= 'extension.xml';
234 }
235
236 // Disable the update site. If the get() below fails with a fatal error (e.g. timeout) the faulty update
237 // site will remain disabled
238 $this->toggleUpdateSite($this->updateSiteId, false);
239
240 $startTime = microtime(true);
241
242 // JHttp transport throws an exception when there's no response.
243 try
244 {
245 $http = JHttpFactory::getHttp();
246 $response = $http->get($url, array(), 20);
247 }
248 catch (RuntimeException $e)
249 {
250 $response = null;
251 }
252
253 // Enable the update site. Since the get() returned the update site should remain enabled
254 $this->toggleUpdateSite($this->updateSiteId, true);
255
256 // Log the time it took to load this update site's information
257 $endTime = microtime(true);
258 $timeToLoad = sprintf('%0.2f', $endTime - $startTime);
259 JLog::add(
260 "Loading information from update site #{$this->updateSiteId} with name " .
261 "\"$this->updateSiteName\" and URL $url took $timeToLoad seconds", JLog::INFO, 'updater'
262 );
263
264 if ($response === null || $response->code !== 200)
265 {
266 // If the URL is missing the .xml extension, try appending it and retry loading the update
267 if (!$this->appendExtension && (substr($url, -4) != '.xml'))
268 {
269 $options['append_extension'] = true;
270
271 return $this->getUpdateSiteResponse($options);
272 }
273
274 // Log the exact update site name and URL which could not be loaded
275 JLog::add('Error opening url: ' . $url . ' for update site: ' . $this->updateSiteName, JLog::WARNING, 'updater');
276 $app = JFactory::getApplication();
277 $app->enqueueMessage(JText::sprintf('JLIB_UPDATER_ERROR_OPEN_UPDATE_SITE', $this->updateSiteId, $this->updateSiteName, $url), 'warning');
278
279 return false;
280 }
281
282 return $response;
283 }
284 }
285