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