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