Marius Cramer
2015-08-06 37b29231e47a0c4458dc1c15d98588f16f07e1e2
commit | author | age
146783 1 <?php
MC 2 /*
3 Copyright (c) 2012, ISPConfig UG
4 Contributors: web wack creations,  http://www.web-wack.at
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
7fe908 30 require_once 'aps_base.inc.php';
146783 31
MC 32 @set_time_limit(0);
33 @ignore_user_abort(1);
34
35 class ApsCrawler extends ApsBase
36 {
37
7fe908 38     //public $app_download_url_list = array();
146783 39
7fe908 40     /**
MC 41      * Constructor
42      *
43      * @param $app the application instance (db handle + log method)
44      * @param $interface_mode act in interface (true) or server mode (false)
45      */
146783 46
MC 47
7fe908 48     public function __construct($app, $interface_mode = false)
MC 49     {
50         parent::__construct($app, 'APS crawler: ', $interface_mode);
51     }
146783 52
MC 53
54
7fe908 55     /**
MC 56      * Before the cron is executed, make sure all necessary options are set
57      * and all functions (i.e. cURL) are available
58      */
59     private function checkRequirements()
60     {
61         global $app;
146783 62
7fe908 63         try
MC 64         {
65             // Check if allow_url_fopen is enabled
66             if(!@ini_get('allow_url_fopen')) throw new Exception('allow_url_fopen is not enabled');
67             // Check if the cURL module is available
68             if(!function_exists('curl_version')) throw new Exception('cURL is not available');
69
70             // Check if used folders are writable
71             if($this->interface_mode)
72             {
73                 if(!is_writable($this->interface_pkg_dir))
74                     throw new Exception('the folder '.basename($this->interface_pkg_dir).' is not writable');
75             }
76             else
77             {
78                 if(!is_writable($this->packages_dir))
79                     throw new Exception('the folder '.basename($this->packages_dir).' is not writable');
80             }
81
82             return true;
83         }
84
85         catch(Exception $e)
86         {
87             $app->log($this->log_prefix.'Aborting execution because '.$e->getMessage(), LOGLEVEL_ERROR);
88             return false;
89         }
90     }
91
92
93
94     /**
95      * Remove a directory recursively
96      * In case of error be silent
97      *
98      * @param $dir the directory to remove
99      */
100     private function removeDirectory($dir)
101     {
102         if(is_dir($dir))
103         {
104             $files = scandir($dir);
105             foreach($files as $file)
106             {
107                 if($file != '.' && $file != '..')
108                     if(filetype($dir.'/'.$file) == 'dir') $this->removeDirectory($dir.'/'.$file);
109                     else @unlink($dir.'/'.$file);
110             }
111             reset($files);
112             @rmdir($dir);
113         }
114     }
115
116
117     /**
118      * Fetch HTML data from one or more given URLs
119      * If a string is given, a string is returned, if an array of URLs should
120      * be fetched, the responses of the parallel queries are returned as array
121      *
122      * @param $input the string or array to fetch
123      * @return $ret a query response string or array
124      */
125     private function fetchPage($input)
126     {
127         $ret = array();
128         $url = array();
129         $conn = array();
130
131         // Make sure we are working with an array, further on
132         if(!is_array($input)) $url[] = $input;
133         else $url = $input;
134
135         // Build the single cURL handles and add them to a multi handle
136         $mh = curl_multi_init();
137         for($i = 0; $i < count($url); $i++)
138         {
139             $conn[$i] = curl_init('http://'.$this->fetch_url.$url[$i]);
140             curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, true);
141             curl_multi_add_handle($mh, $conn[$i]);
142         }
143
144         $active = 0;
145         do curl_multi_exec($mh, $active);
146         while($active > 0);
147
148         // Get the response(s)
149         for($i = 0; $i < count($url); $i++)
150         {
151             $ret[$i] = curl_multi_getcontent($conn[$i]);
152             curl_multi_remove_handle($mh, $conn[$i]);
153             curl_close($conn[$i]);
154         }
155         curl_multi_close($mh);
156
157         if(count($url) == 1) $ret = $ret[0];
158
159         return $ret;
160     }
161
162
163
164     /**
165      * Fetch binary data from a given array
166      * The data is retrieved in binary mode and
167      * then directly written to an output file
168      *
169      * @param $input a specially structed array
170      * @see $this->startUpdate()
171      */
172     private function fetchFiles($input)
173     {
174         $fh = array();
175         $url = array();
176         $conn = array();
177
178         // Build the single cURL handles and add them to a multi handle
179         $mh = curl_multi_init();
180
181         // Process each app
182         for($i = 0; $i < count($input); $i++)
183         {
184             $conn[$i] = curl_init($input[$i]['url']);
185             $fh[$i] = fopen($input[$i]['localtarget'], 'wb');
186
187             curl_setopt($conn[$i], CURLOPT_BINARYTRANSFER, true);
188             curl_setopt($conn[$i], CURLOPT_FILE, $fh[$i]);
189             curl_setopt($conn[$i], CURLOPT_TIMEOUT, 0);
190             curl_setopt($conn[$i], CURLOPT_FAILONERROR, 1);
191             curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION, 1);
6c1627 192             curl_setopt($conn[$i], CURLOPT_SSL_VERIFYHOST, 1);
TB 193             curl_setopt($conn[$i], CURLOPT_SSL_VERIFYPEER, false);
7fe908 194
MC 195             curl_multi_add_handle($mh, $conn[$i]);
196         }
197
198         $active = 0;
199         do curl_multi_exec($mh, $active);
200         while($active > 0);
201
202         // Close the handles
203         for($i = 0; $i < count($input); $i++)
204         {
205             fclose($fh[$i]);
206             curl_multi_remove_handle($mh, $conn[$i]);
207             curl_close($conn[$i]);
208         }
209         curl_multi_close($mh);
210     }
211
212
213
214     /**
215      * A method to build query URLs out of a list of vendors
216      *
217      */
218     private function formatVendorCallback($array_item)
219     {
220         $array_item = str_replace(' ', '%20', $array_item);
221         $array_item = str_replace('http://', '', $array_item);
222         $array_item = '/'.$this->aps_version.'.atom?vendor='.$array_item.'&pageSize=100';
223         return $array_item;
224     }
225
226
227
228     /**
229      * The main method which performs the actual crawling
230      */
231     public function startCrawler()
232     {
233         global $app;
234
235         try
236         {
237             // Make sure the requirements are given so that this script can execute
238             $req_ret = $this->checkRequirements();
239             if(!$req_ret) return false;
240
241             // Execute the open task and first fetch all vendors (APS catalog API 1.1, p. 12)
242             $app->log($this->log_prefix.'Fetching data from '.$this->fetch_url);
243
244             $vendor_page = $this->fetchPage('/all-app/'); //$vendor_page = $this->fetchPage('/'.$this->aps_version.'/');
245             preg_match_all("/\<a href=\"(.+)\/\" class=\"vendor\"/i", $vendor_page, $matches);
246             $vendors = array_map('urldecode', $matches[1]);
247             if(!$vendors) throw new Exception('Unable to fetch vendors. Aborting');
248
249             // Format all vendors for further processing (i.e. typo3.org -> /1.atom?vendor=typo3.org&pageSize=100
250             //array_walk($vendors, array($this, 'formatVendorCallback'));
146783 251             if(is_array($vendors)) {
MC 252                 foreach($vendors as $key => $array_item) {
253                     $vendors[$key] = $this->formatVendorCallback($array_item);
254                 }
255             }
256
7fe908 257             // Process all vendors in chunks of 50 entries
MC 258             $vendor_chunks = array_chunk($vendors, 50);
259             //var_dump($vendor_chunks);
260
261             // Get all known apps from the database and the highest known version
262             // Note: A dirty hack is used for numerical sorting of the VARCHAR field Version: +0 -> cast
263             // A longer but typesafe way would be: ORDER BY CAST(REPLACE(Version, '.', '') AS UNSIGNED) DESC
264             $existing_apps = $app->db->queryAllRecords("SELECT * FROM (
265                 SELECT name AS Name, CONCAT(version, '-', CAST(`release` AS CHAR)) AS CurrentVersion
146783 266                 FROM aps_packages ORDER BY REPLACE(version, '.', '')+0 DESC, `release` DESC
MC 267                 ) as Versions GROUP BY name");
7fe908 268             //var_dump($existing_apps);
MC 269
270             // Used for statistics later
271             $apps_in_repo = 0;
272             $apps_updated = 0;
273             $apps_downloaded = 0;
274
275             $apps_to_dl = array();
276
277             for($i = 0; $i < count($vendor_chunks); $i++)
278             {
279                 // Fetch all apps for the current chunk of vendors
280                 $apps = $this->fetchPage($vendor_chunks[$i]);
281
282                 for($j = 0; $j < count($apps); $j++)
283                 {
284                     // Before parsing, make sure it's worth the work by checking if at least one app exists
285                     $apps_count = substr_count($apps[$j], '<opensearch:totalResults>0</opensearch:totalResults>');
286                     if($apps_count == 0) // obviously this vendor provides one or more apps
287                         {
6c1627 288                         try {
TB 289                             // Rename namespaces and register them
290                             $xml = str_replace("xmlns=", "ns=", $apps[$j]);
291                             $sxe = new SimpleXMLElement($xml);
292                             $namespaces = $sxe->getDocNamespaces(true);
293                             foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url);
54d081 294                         
6c1627 295                             //Find highest version
TB 296                             $app_version = "0.0.0";
297                             $entry_pos = 1;
298                             for ($p = 1; ; $p++) {
299                                 $app_version_tmp = parent::getXPathValue($sxe, 'entry[position()=' . $p . ']/a:version');
300                                 if (strlen($app_version_tmp) < 1) break;
301                                 if (version_compare($app_version_tmp, $app_version) >= 0) {
302                                     $app_version = $app_version_tmp;
303                                     $entry_pos = $p;
304                                 }
54d081 305                             }
7fe908 306
6c1627 307                             // Fetching values of interest
TB 308                             //$app_name = parent::getXPathValue($sxe, 'entry[position()=1]/a:name');
309                             //$app_version = parent::getXPathValue($sxe, 'entry[position()=1]/a:version');
310                             //$app_release = parent::getXPathValue($sxe, 'entry[position()=1]/a:release');
311                             $app_name = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/a:name");
312                             $app_version = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/a:version");
313                             $app_release = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/a:release");
7fe908 314
6c1627 315                             // Find out a (possibly) existing package version
TB 316                             $ex_ver = '';
317                             /*
318                             array_walk($existing_apps,
319                                 create_function('$v, $k, $ex_ver', 'if($v["Name"] == "'.$app_name.'") $ex_ver = $v["CurrentVersion"];'), &$ex_ver);
320                             */
321                             if(is_array($existing_apps)) {
322                                 foreach($existing_apps as $k => $v) {
323                                     if($v["Name"] == $app_name) $ex_ver = $v["CurrentVersion"];
324                                 }
146783 325                             }
MC 326
6c1627 327                             $new_ver = $app_version.'-'.$app_release;
TB 328                             $local_intf_folder = $this->interface_pkg_dir.'/'.$app_name.'-'.$new_ver.'.app.zip/';
146783 329
6c1627 330                             // Proceed if a newer or at least equal version has been found with server mode or
TB 331                             // interface mode is activated and there are no valid APP-META.xml and PKG_URL existing yet
332                             if((!$this->interface_mode && version_compare($new_ver, $ex_ver) >= 0) || ($this->interface_mode && (!file_exists($local_intf_folder.'APP-META.xml') || filesize($local_intf_folder.'APP-META.xml') == 0 || !file_exists($local_intf_folder.'PKG_URL') || filesize($local_intf_folder.'PKG_URL') == 0))){
333                                 // Check if we already have an old version of this app
334                                 if(!empty($ex_ver) && version_compare($new_ver, $ex_ver) == 1) $apps_updated++;
7fe908 335
6c1627 336                                 //$app_dl = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='aps']/@href");
TB 337                                 //$app_filesize = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='aps']/@length");
338                                 //$app_metafile = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='meta']/@href");
339                                 $app_dl = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/link[@a:type='aps']/@href");
340                                 $app_filesize = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/link[@a:type='aps']/@length");
341                                 $app_metafile = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/link[@a:type='meta']/@href");
7fe908 342
6c1627 343                                 //$this->app_download_url_list[$app_name.'-'.$new_ver.'.app.zip'] = $app_dl;
TB 344                                 // Skip ASP.net packages because they can't be used at all
345                                 $asp_handler = parent::getXPathValue($sxe, '//aspnet:handler');
346                                 $asp_permissions = parent::getXPathValue($sxe, '//aspnet:permissions');
347                                 $asp_version = parent::getXPathValue($sxe, '//aspnet:version');
348                                 if(!empty($asp_handler) || !empty($asp_permissions) || !empty($asp_version)) continue;
146783 349
6c1627 350                                 // Interface mode (download only parts)
TB 351                                 if($this->interface_mode)
7fe908 352                                 {
6c1627 353                                     // Delete an obviously out-dated version from the system and DB
TB 354                                     if(!empty($ex_ver) && version_compare($new_ver, $ex_ver) == 1)
7fe908 355                                     {
6c1627 356                                         $old_folder = $this->interface_pkg_dir.'/'.$app_name.'-'.$ex_ver.'.app.zip';
TB 357                                         if(file_exists($old_folder)) $this->removeDirectory($old_folder);
358
cc7a82 359                                         $tmp = $app->db->queryOneRecord("SELECT id FROM aps_packages WHERE name = ? AND CONCAT(version, '-', CAST(`release` AS CHAR)) = ?", $app_name, $ex_ver);
3a11d2 360                                         $app->db->datalogUpdate('aps_packages', array("package_status" => PACKAGE_OUTDATED), 'id', $tmp['id']);
6c1627 361                                         unset($tmp);
7fe908 362                                     }
MC 363
6c1627 364                                     // Create the local folder if not yet existing
TB 365                                     if(!file_exists($local_intf_folder)) @mkdir($local_intf_folder, 0777, true);
366
367                                     // Save the package URL in an extra file because it's not part of the APP-META.xml file
368                                     @file_put_contents($local_intf_folder.'PKG_URL', $app_dl);
369
370                                     // Download the meta file
371                                     $local_metafile = $local_intf_folder.'APP-META.xml';
372                                     if(!file_exists($local_metafile) || filesize($local_metafile) == 0)
7fe908 373                                     {
6c1627 374                                         $apps_to_dl[] = array('name' => 'APP-META.xml',
TB 375                                             'url' => $app_metafile,
7fe908 376                                             'filesize' => 0,
6c1627 377                                             'localtarget' => $local_metafile);
TB 378                                         $apps_downloaded++;
7fe908 379                                     }
MC 380
6c1627 381                                     // Download package license
TB 382                                     //$license = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='eula']/@href");
383                                     $license = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/link[@a:type='eula']/@href");
384                                     if($license != '')
7fe908 385                                     {
6c1627 386                                         $local_license = $local_intf_folder.'LICENSE';
TB 387                                         if(!file_exists($local_license) || filesize($local_license) == 0)
7fe908 388                                         {
6c1627 389                                             $apps_to_dl[] = array('name' => basename($license),
TB 390                                                 'url' => $license,
7fe908 391                                                 'filesize' => 0,
6c1627 392                                                 'localtarget' => $local_license);
TB 393                                         }
394                                     }
395
396                                     // Download package icon
397                                     //$icon = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='icon']/@href");
398                                     $icon = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/link[@a:type='icon']/@href");
399                                     if($icon != '')
400                                     {
401                                         $local_icon = $local_intf_folder.basename($icon);
402                                         if(!file_exists($local_icon) || filesize($local_icon) == 0)
403                                         {
404                                             $apps_to_dl[] = array('name' => basename($icon),
405                                                 'url' => $icon,
406                                                 'filesize' => 0,
407                                                 'localtarget' => $local_icon);
408                                         }
409                                     }
410
411                                     // Download available screenshots
412                                     //$screenshots = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='screenshot']", true);
413                                     $screenshots = parent::getXPathValue($sxe, "entry[position()=" . $entry_pos . "]/link[@a:type='screenshot']", true);
414                                     if(!empty($screenshots))
415                                     {
416                                         foreach($screenshots as $screen)
417                                         {
418                                             $local_screen = $local_intf_folder.basename($screen['href']);
419                                             if(!file_exists($local_screen) || filesize($local_screen) == 0)
420                                             {
421                                                 $apps_to_dl[] = array('name' => basename($screen['href']),
422                                                     'url' => $screen['href'],
423                                                     'filesize' => 0,
424                                                     'localtarget' => $local_screen);
425                                             }
7fe908 426                                         }
MC 427                                     }
428                                 }
6c1627 429                                 else // Server mode (download whole ZIP archive)
TB 430                                     {
431                                     // Delete an obviously out-dated version from the system
432                                     if(!empty($ex_ver) && version_compare($new_ver, $ex_ver) == 1)
433                                     {
434                                         $old_file = $this->packages_dir.'/'.$app_name.'-'.$ex_ver.'.app.zip';
435                                         if(file_exists($old_file)) $this->removeDirectory($old_file);
436                                     }
7fe908 437
6c1627 438                                     // Attention: $new_ver can also be == $ex_ver (according to version_compare >= 0)
TB 439                                     $local_zip = $this->packages_dir.'/'.$app_name.'-'.$new_ver.'.app.zip';
7fe908 440
6c1627 441                                     // Before re-downloading a file, make sure it's not yet existing on HDD (due to DB inconsistency)
TB 442                                     if((file_exists($local_zip) && (filesize($local_zip) == $app_filesize)) === false)
443                                     {
444                                         $apps_to_dl[] = array('name' => $app_name,
445                                             'url' => $app_dl,
446                                             'filesize' => $app_filesize,
447                                             'localtarget' => $local_zip);
448                                         $apps_downloaded++;
449                                     }
7fe908 450                                 }
MC 451                             }
6c1627 452
TB 453                             unset($sxe);
454                             $apps_in_repo++;
455                         } catch (Exception $e) {
456                             // We dont want the crawler to fail on xml parse errors
457                             $app->log($this->log_prefix.$e->getMessage(), LOGLEVEL_WARN);
458                             //echo 'Caught exception: ',  $e->getMessage(), "\n";
7fe908 459                         }
MC 460                     }
461                 }
462                 //var_dump($apps);
42f0c9 463                 //echo print_r($apps_to_dl).'<br>-------------------<br>';
7fe908 464
MC 465                 // For memory reasons, unset the current vendor and his apps
466                 unset($apps);
467             }
468
469             // Shuffle the download array (in order to compensate unexpected php aborts)
470             shuffle($apps_to_dl);
471
472             // After collecting all provisioned apps, download them
473             $apps_to_dl_chunks = array_chunk($apps_to_dl, 10);
474
475             for($i = 0; $i < count($apps_to_dl_chunks); $i++)
476             {
477                 $this->fetchFiles($apps_to_dl_chunks[$i]);
478
479                 // Check the integrity of all downloaded files
480                 // but exclude cases where no filesize is available (i.e. screenshot or metafile download)
481                 for($j = 0; $j < count($apps_to_dl_chunks[$i]); $j++)
482                 {
483                     if($apps_to_dl_chunks[$i][$j]['filesize'] != 0 &&
484                         $apps_to_dl_chunks[$i][$j]['filesize'] != filesize($apps_to_dl_chunks[$i][$j]['localtarget']))
485                     {
486                         $app->log($this->log_prefix.' The filesize of the package "'.
487                             $apps_to_dl_chunks[$i][$j]['name'].'" is wrong. Download failure?', LOGLEVEL_WARN);
488                     }
489                 }
490             }
491
492             $app->log($this->log_prefix.'Processed '.$apps_in_repo.
493                 ' apps from the repo. Downloaded '.$apps_updated.
494                 ' updates, '.$apps_downloaded.' new apps');
495         }
496
497         catch(Exception $e)
498         {
499             $app->log($this->log_prefix.$e->getMessage(), LOGLEVEL_ERROR);
500             return false;
501         }
502     }
503
504
505
506     /**
507      * Read in all possible packages from the interface packages folder and
508      * check if they are not ASP.net code (as this can't be processed).
509      *
510      * Note: There's no need to check if the packages to register are newer
511      * than those in the database because this already happended in startCrawler()
512      */
513     public function parseFolderToDB()
514     {
515         global $app;
516
517         try
518         {
519             // This method must be used in interface mode
520             if(!$this->interface_mode) return false;
521
522             $pkg_list = array();
523
524             // Read in every package having a correct filename
525             $temp_handle = @dir($this->interface_pkg_dir);
526             if(!$temp_handle) throw new Exception('The temp directory is not accessible');
527             while($folder = $temp_handle->read())
528                 if(substr($folder, -8) == '.app.zip') $pkg_list[] = $folder;
529                 $temp_handle->close();
530
531             // If no packages are available -> exception (because at this point there should exist packages)
532             if(empty($pkg_list)) throw new Exception('No packages to read in');
533
534             // Get registered packages and mark non-existant packages with an error code to omit the install
535             $existing_packages = array();
cc7a82 536             $path_query = $app->db->queryAllRecords('SELECT path AS Path FROM aps_packages');
7fe908 537             foreach($path_query as $path) $existing_packages[] = $path['Path'];
MC 538             $diff = array_diff($existing_packages, $pkg_list);
539             foreach($diff as $todelete) {
cc7a82 540                 $tmp = $app->db->queryOneRecord("SELECT id FROM aps_packages WHERE path = ?", $todelete);
3a11d2 541                 $app->db->datalogUpdate('aps_packages', array("package_status" => PACKAGE_ERROR_NOMETA), 'id', $tmp['id']);
146783 542                 unset($tmp);
MC 543             }
7fe908 544
MC 545             // Register all new packages
546             $new_packages = array_diff($pkg_list, $existing_packages);
547             foreach($new_packages as $pkg)
548             {
549                 // Load in meta file if existing and register its namespaces
550                 $metafile = $this->interface_pkg_dir.'/'.$pkg.'/APP-META.xml';
551                 if(!file_exists($metafile))
552                 {
553                     $app->log($this->log_prefix.'Cannot read metadata from '.$pkg, LOGLEVEL_ERROR);
554                     continue;
555                 }
556
557                 $metadata = file_get_contents($metafile);
558                 $metadata = str_replace("xmlns=", "ns=", $metadata);
559                 $sxe = new SimpleXMLElement($metadata);
560                 $namespaces = $sxe->getDocNamespaces(true);
561                 foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url);
562
563                 // Insert the new package
564                 $pkg_name = parent::getXPathValue($sxe, 'name');
565                 $pkg_category = parent::getXPathValue($sxe, '//category');
566                 $pkg_version = parent::getXPathValue($sxe, 'version');
567                 $pkg_release = parent::getXPathValue($sxe, 'release');
146783 568                 //$pkg_url = $this->app_download_url_list[$pkg];
MC 569                 $pkg_url = @file_get_contents($this->interface_pkg_dir.'/'.$pkg.'/PKG_URL');
7fe908 570
146783 571                 // Insert only if data is complete
MC 572                 if($pkg != '' && $pkg_name != '' && $pkg_category != '' && $pkg_version != '' && $pkg_release != '' && $pkg_url){
3a11d2 573                     $insert_data = array(
MC 574                         "path" => $pkg,
575                         "name" => $pkg_name,
576                         "category" => $pkg_category,
577                         "version" => $pkg_version,
578                         "release" => $pkg_release,
579                         "package_url" => $pkg_url,
580                         "package_status" => PACKAGE_ENABLED
581                     );
146783 582                     $app->db->datalogInsert('aps_packages', $insert_data, 'id');
MC 583                 } else {
584                     if(file_exists($this->interface_pkg_dir.'/'.$pkg)) $this->removeDirectory($this->interface_pkg_dir.'/'.$pkg);
585                 }
7fe908 586             }
MC 587         }
588
589         catch(Exception $e)
590         {
591             $app->log($this->log_prefix.$e->getMessage(), LOGLEVEL_ERROR);
146783 592             $app->error($e->getMessage());
7fe908 593             return false;
MC 594         }
595     }
596
597
598
146783 599     /**
7fe908 600      * Add missing package URLs to database
MC 601      */
602     public function fixURLs()
603     {
604         global $app;
605
606         try
607         {
608             // This method must be used in interface mode
609             if(!$this->interface_mode) return false;
610
cc7a82 611             $incomplete_pkgs = $app->db->queryAllRecords("SELECT * FROM aps_packages WHERE package_url = ?", '');
146783 612             if(is_array($incomplete_pkgs) && !empty($incomplete_pkgs)){
MC 613                 foreach($incomplete_pkgs as $incomplete_pkg){
614                     $pkg_url = @file_get_contents($this->interface_pkg_dir.'/'.$incomplete_pkg['path'].'/PKG_URL');
615                     if($pkg_url != ''){
2af58c 616                         $app->db->datalogUpdate('aps_packages', array("package_url" => $pkg_url), 'id', $incomplete_pkg['id']);
146783 617                     }
MC 618                 }
619             }
7fe908 620         }
MC 621
622         catch(Exception $e)
623         {
624             $app->log($this->log_prefix.$e->getMessage(), LOGLEVEL_ERROR);
146783 625             $app->error($e->getMessage());
7fe908 626             return false;
MC 627         }
628     }
629
146783 630 }
7fe908 631
MC 632 ?>