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 * Collection Update Adapter Class
16 *
17 * @since 11.1
18 */
19 class JUpdaterCollection extends JUpdateAdapter
20 {
21 /**
22 * Root of the tree
23 *
24 * @var object
25 * @since 11.1
26 */
27 protected $base;
28
29 /**
30 * Tree of objects
31 *
32 * @var array
33 * @since 11.1
34 */
35 protected $parent = array(0);
36
37 /**
38 * Used to control if an item has a child or not
39 *
40 * @var boolean
41 * @since 11.1
42 */
43 protected $pop_parent = 0;
44
45 /**
46 * @var array A list of discovered update sites
47 */
48 protected $update_sites;
49
50 /**
51 * A list of discovered updates
52 *
53 * @var array
54 */
55 protected $updates;
56
57 /**
58 * Gets the reference to the current direct parent
59 *
60 * @return object
61 *
62 * @since 11.1
63 */
64 protected function _getStackLocation()
65 {
66 return implode('->', $this->stack);
67 }
68
69 /**
70 * Get the parent tag
71 *
72 * @return string parent
73 *
74 * @since 11.1
75 */
76 protected function _getParent()
77 {
78 return end($this->parent);
79 }
80
81 /**
82 * Opening an XML element
83 *
84 * @param object $parser Parser object
85 * @param string $name Name of element that is opened
86 * @param array $attrs Array of attributes for the element
87 *
88 * @return void
89 *
90 * @since 11.1
91 */
92 public function _startElement($parser, $name, $attrs = array())
93 {
94 $this->stack[] = $name;
95 $tag = $this->_getStackLocation();
96
97 // Reset the data
98 if (isset($this->$tag))
99 {
100 $this->$tag->_data = '';
101 }
102
103 switch ($name)
104 {
105 case 'CATEGORY':
106 if (isset($attrs['REF']))
107 {
108 $this->update_sites[] = array('type' => 'collection', 'location' => $attrs['REF'], 'update_site_id' => $this->updateSiteId);
109 }
110 else
111 {
112 // This item will have children, so prepare to attach them
113 $this->pop_parent = 1;
114 }
115 break;
116 case 'EXTENSION':
117 $update = JTable::getInstance('update');
118 $update->set('update_site_id', $this->updateSiteId);
119
120 foreach ($this->updatecols as $col)
121 {
122 // Reset the values if it doesn't exist
123 if (!array_key_exists($col, $attrs))
124 {
125 $attrs[$col] = '';
126
127 if ($col == 'CLIENT')
128 {
129 $attrs[$col] = 'site';
130 }
131 }
132 }
133
134 $client = JApplicationHelper::getClientInfo($attrs['CLIENT'], 1);
135
136 if (isset($client->id))
137 {
138 $attrs['CLIENT_ID'] = $client->id;
139 }
140
141 // Lower case all of the fields
142 foreach ($attrs as $key => $attr)
143 {
144 $values[strtolower($key)] = $attr;
145 }
146
147 // Only add the update if it is on the same platform and release as we are
148 $ver = new JVersion;
149
150 // Lower case and remove the exclamation mark
151 $product = strtolower(JFilterInput::getInstance()->clean($ver::PRODUCT, 'cmd'));
152
153 /*
154 * Set defaults, the extension file should clarify in case but it may be only available in one version
155 * This allows an update site to specify a targetplatform
156 * targetplatformversion can be a regexp, so 1.[56] would be valid for an extension that supports 1.5 and 1.6
157 * Note: Whilst the version is a regexp here, the targetplatform is not (new extension per platform)
158 * Additionally, the version is a regexp here and it may also be in an extension file if the extension is
159 * compatible against multiple versions of the same platform (e.g. a library)
160 */
161 if (!isset($values['targetplatform']))
162 {
163 $values['targetplatform'] = $product;
164 }
165
166 // Set this to ourself as a default
167 if (!isset($values['targetplatformversion']))
168 {
169 $values['targetplatformversion'] = $ver::RELEASE;
170 }
171
172 // Set this to ourselves as a default
173 // validate that we can install the extension
174 if ($product == $values['targetplatform'] && preg_match('/^' . $values['targetplatformversion'] . '/', JVERSION))
175 {
176 $update->bind($values);
177 $this->updates[] = $update;
178 }
179 break;
180 }
181 }
182
183 /**
184 * Closing an XML element
185 * Note: This is a protected function though has to be exposed externally as a callback
186 *
187 * @param object $parser Parser object
188 * @param string $name Name of the element closing
189 *
190 * @return void
191 *
192 * @since 11.1
193 */
194 protected function _endElement($parser, $name)
195 {
196 array_pop($this->stack);
197
198 switch ($name)
199 {
200 case 'CATEGORY':
201 if ($this->pop_parent)
202 {
203 $this->pop_parent = 0;
204 array_pop($this->parent);
205 }
206 break;
207 }
208 }
209
210 // Note: we don't care about char data in collection because there should be none
211
212 /**
213 * Finds an update
214 *
215 * @param array $options Options to use: update_site_id: the unique ID of the update site to look at
216 *
217 * @return array Update_sites and updates discovered
218 *
219 * @since 11.1
220 */
221 public function findUpdate($options)
222 {
223 $response = $this->getUpdateSiteResponse($options);
224
225 if ($response === false)
226 {
227 return false;
228 }
229
230 $this->xmlParser = xml_parser_create('');
231 xml_set_object($this->xmlParser, $this);
232 xml_set_element_handler($this->xmlParser, '_startElement', '_endElement');
233
234 if (!xml_parse($this->xmlParser, $response->body))
235 {
236 // If the URL is missing the .xml extension, try appending it and retry loading the update
237 if (!$this->appendExtension && (substr($this->_url, -4) != '.xml'))
238 {
239 $options['append_extension'] = true;
240
241 return $this->findUpdate($options);
242 }
243
244 JLog::add('Error parsing url: ' . $this->_url, JLog::WARNING, 'updater');
245
246 $app = JFactory::getApplication();
247 $app->enqueueMessage(JText::sprintf('JLIB_UPDATER_ERROR_COLLECTION_PARSE_URL', $this->_url), 'warning');
248
249 return false;
250 }
251
252 // TODO: Decrement the bad counter if non-zero
253 return array('update_sites' => $this->update_sites, 'updates' => $this->updates);
254 }
255 }
256