Test for open_ssl in extensions.
[fa-stable.git] / includes / packages.inc
1 <?php
2 /**********************************************************************
3     Copyright (C) FrontAccounting, LLC.
4         Released under the terms of the GNU General Public License, GPL, 
5         as published by the Free Software Foundation, either version 3 
6         of the License, or (at your option) any later version.
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
10     See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
11 ***********************************************************************/
12 include_once($path_to_root. "/includes/archive.inc");
13 include_once($path_to_root. "/includes/remote_url.inc");
14 include_once($path_to_root. "/includes/hooks.inc");
15
16 define('PKG_CACHE_PATH', $path_to_root.'/modules/_cache');
17 define('PUBKEY_PATH', $path_to_root);
18 //
19 // FrontAccounting package class
20 //
21 class package extends gzip_file {
22         function package($filename, $basedir=null)
23         {
24                 global $path_to_root;
25
26                 if (!$basedir) {
27                         $basedir = PKG_CACHE_PATH.'/'.substr(basename($filename), 0, -4);
28                         if (file_exists($basedir)) {
29 //                              flush_dir($basedir, true); 
30                         } else
31                         mkdir($basedir);
32                 }
33                 $this->archive($filename);
34                 $this->set_options(array('basedir'=> $basedir));
35                 $this->options['type'] = "pkg";
36         }
37         //
38         //      Used by archive class. Use create_archive() instead.
39         //      
40         function create_pkg() 
41         {
42                 return $this->create_gzip();
43         }
44         //
45         //      Install package and clean temp directory.
46         //
47         function install()
48         {
49                 global $path_to_root;
50                 
51                 $success = true;
52
53                 $this->set_options(array('overwrite' => 1));
54                 $this->extract_files(); // extract package in cache directory
55                 $cachepath = $this->options['basedir'];
56                 $ctrl = get_control_file("$cachepath/_init/config");
57
58                 $targetdir = $path_to_root.'/'.$ctrl['InstallPath'];
59
60                 if (!is_dir($targetdir))
61                         mkdir($targetdir);
62
63                 $dpackage = new package("$cachepath/_data", $targetdir);
64                 $dpackage->set_options(array('overwrite' => 1));
65
66                 $flist = $dpackage->extract_files(true);
67                 if (count($dpackage->error)) {
68                         $this->error = array_merge($this->error, $dpackage->error);
69                         return false;
70                 }
71                 copy_files($flist, $targetdir, "$cachepath/_back");
72         
73                 $dpackage->extract_files(); //install package in target directory
74
75                 $install = hook_invoke($ctrl['Package'], 'install_extension', $dummy);
76                 $success &= $install===null || $install;
77                 $success &= count($dpackage->error) == 0;
78                 $this->error = array_merge($this->error, $dpackage->error);
79                 return $success;
80         }
81         //
82         //      Removing package related sources
83         //
84         function uninstall()
85         {
86                 global $path_to_root;
87
88                 $success = true;
89
90                 $cachepath = $this->options['basedir'];
91                 $ctrl = get_control_file("$cachepath/_init/config");
92
93                 $targetdir = $path_to_root.'/'.$ctrl['InstallPath'];
94
95                 $dpackage = new package("$cachepath/_data", $targetdir);
96
97                 $flist = $dpackage->extract_files(true);
98
99                 $success &= copy_files($flist, "$cachepath/_back", $targetdir, true);
100
101                 if (strpos($ctrl['InstallPath'], 'modules/') === 0) { // flush module directory
102                         flush_dir($targetdir, true);
103                         rmdir($targetdir);
104                 }
105
106                 $uninstall = hook_invoke($ctrl['Package'], 'uninstall_extension', $dummy);
107                 $success &= $uninstall===null || $uninstall;
108
109                 return $success;
110         }
111         //
112         //      Purge all package related configuration and data.
113         //
114         function purge()
115         {
116                 return true;
117         }
118
119 }
120 //
121 // Changes field value read from control file (single, or multiline) into 
122 // arrays of subfields if needed.
123 //
124 function ufmt_property($key, $value)
125 {
126         // indexes used in output arrays
127         $sub_fields = array(
128 //              'MenuTabs' => array('url', 'access', 'tab_id', 'title', 'section'),
129 //              'MenuEntries' => array('url', 'access', 'tab_id', 'title'),
130         );
131         if (!isset($sub_fields[$key]))
132                 return $value==='' ? null : $value;
133
134         $prop = array();
135
136         if (!is_array($value))
137                 $value = array($value);
138         foreach($value as $line) {
139                 $indexes = $sub_fields[$key];
140                 $ret = array();
141                 preg_match_all('/(["])(?:\\\\?+.)*?\1|[^"\s][\S]*/', $line, $match);
142                 foreach($match[0] as $n => $subf) {
143                         if ($match[1][$n])
144                                 $val = strtr(substr($subf, 1, -1),
145                                         array('\\"'=>'"'));
146                 else
147                                 $val = $subf;
148                         if (count($indexes))
149                                 $ret[array_shift($indexes)] = $val;
150                         else
151                                 $ret[] = $val;
152                 }
153                 if (count($ret))
154                         $prop[] = $ret;
155         }
156         return $prop;
157 }
158 //=============================================================================
159 //
160 // Retrieve control file and return as associative array
161 //      $index is name of field used as key in result array, or null for numeric keys
162 //
163 function get_control_file($file, $index = false) {
164
165         $list = gzopen($file, 'rb');
166         if (!$list) return null;
167
168         $repo = $pkg = array();
169         $key = false; $value = '';
170         $line = '';
171         do {
172                 $line = rtrim($line);
173                 if ($line && ctype_space($line[0])) { // continuation of multiline property
174                         if (strlen(ltrim($line))) {
175                                 if ($value !== '' && !is_array($value))
176                                         $value = array($value);
177                                 $value[] = ltrim($line);
178                                 continue;
179                         }
180                 }
181                 if ($key) { // save previous property if any
182                         $pkg[$key] = ufmt_property($key, $value);
183                 }
184                 if (!strlen($line)) { // end of section
185                         if (count($pkg)) {
186                                 if ($index !== true) {
187                                         if ($index === false) break;
188                                         if (!isset($pkg[$index])) {
189                                                 display_error(sprintf(_("No key field '%s' in file '%s'"), $index, $file));
190                                                 return null;
191                                         }
192                                         $repo[$pkg[$index]] = $pkg;
193                                 } else
194                                         $repo[] = $pkg;
195                         }
196                         $pkg = array(); 
197                         $key = null; $value = '';
198                         continue;
199                 } elseif (preg_match('/([^:]*):\s*(.*)/', $line, $m)) {
200                         $key = $m[1]; $value = $m[2];
201                         if (!strlen($key)) {
202                                 display_error("Empty key in line $line");
203                                 return null;
204                         }
205                 } else {
206                         display_error("File parse error in line $line");
207                         return null;
208                 }
209                 
210         } while ((($line = fgets($list))!==false) || $key);
211         fclose($list);
212
213         return $index === false ? $pkg : $repo;
214 }
215 //
216 //      Save configuration data to control file.
217 //
218 function save_control_file($fname, $list, $zip=false) 
219 {
220         $file = $zip ?  gzopen($fname, 'wb') : fopen($fname, 'wb');
221         foreach($list as $section) {
222                 foreach($section as $key => $value) {
223                         if (is_array($value)) { // multiline value
224                                 if (is_array(reset($value))) { // lines have subfields
225                                         foreach($value as $i => $line) {
226                 // Subfields containing white spaces or double quotes are doublequoted 
227                 // with " escaped with backslash.
228                                                 foreach($line as $n => $subfield)
229                                                         if (preg_match('/[\s"]/', $subfield)) {
230                                                                 $value[$i][$n] = 
231                                                                         '"'.strtr($subfield, array('"'=>'\\"')).'"';
232                                                         }
233                                                 // Subfields are separated by white space.
234                                                 $value[$i] = implode(' ', $value[$i]);
235                                         }
236                                 }
237                                 // array elements on subsequent lines starting with white space
238                                 $value = implode("\n ", $value);
239                         }
240                         $zip ? gzwrite($file, "$key: $value\n") : fwrite($file, "$key: $value\n");
241                 }
242                 $zip ? gzwrite($file, "\n"): fwrite($file, "\n");
243         }
244         $zip ? gzclose($file) : fclose($file);
245 }
246 //
247 //      Retrieve text field in localized version or default one 
248 //      when the localized is not avaialable.
249 //
250 function pkg_prop($pkg, $property, $lang=false) 
251 {
252         
253         if ($lang && isset($pkg[$property.'-'.user_language()]))
254                 $prop = @$pkg[$pname];
255         else
256                 $prop = @$pkg[$property];
257
258         return is_array($prop) ? implode("\n ",$prop): $prop;
259 }
260 //
261 //      Retrieve list of packages from repository and return as table ($pkgname==null),
262 //      or find $pkgname package in repository and optionaly download
263 //
264 //      $type is type/s of package
265 //  $filter is optional field selection array in form field=>newkey
266 //              or (0=>field1, 1=>field2...)
267 //  $outkey - field used as a key in package list. If null 'Package' field is used.
268 //
269 function get_pkg_or_list($type = null, $pkgname = null, $filter=array(), $outkey=null, $download=true) {
270
271         global $path_to_root, $repo_auth;
272
273         $repo =  (isset($repo_auth['scheme']) ? $repo_auth['scheme'] : 'http://')
274                         .(isset($repo_auth['login'])  ? $repo_auth['login'].':' : '')
275                         .(isset($repo_auth['pass'])   ? $repo_auth['pass'].'@' : '')
276                         .(isset($repo_auth['host'])   ? $repo_auth['host'].'/' : '')
277                         .(isset($repo_auth['path'])   ? $repo_auth['path'].'/' : '')
278                         .$repo_auth['branch'];
279
280         // first download local copy of repo release file
281         // and check remote signature with local copy of public key
282         //
283         $loclist = PKG_CACHE_PATH.'/Release.gz';
284         $target_dir = $download==true ? "$path_to_root/tmp/" : $download;
285
286         if (isset($type) && !is_array($type)) {
287                 $type = array($type);
288         }
289         $refresh = true;
290         do{
291                 if (!file_exists($loclist)) {
292                         if (!url_copy($repo.'/Release.gz', $loclist))
293                         {
294                                 display_error(_("Cannot download repo index file." ));
295                                 return null;
296                         }
297                         $refresh = false;
298                 }
299                 $sig = url_get_contents($repo.'/Release.sig');
300                 $data = file_get_contents($loclist);
301                 $cert = file_get_contents(PUBKEY_PATH.'/FA.pem');
302                 if (!function_exists('openssl_verify')) {
303                         display_error(_("OpenSSL have to be available on your server to use extension repository system."));
304                         return null;
305                 }       
306                 if (openssl_verify($data, $sig, $cert) <= 0) {
307                         if ($refresh) {
308                                 if (!@unlink($loclist))
309                                 {
310                                         display_error(sprintf(_("Cannot delete outdated '%s' file."), $loclist));
311                                         return null;
312                                 }
313                         } else {
314                                 display_error(_('Release file in repository is invalid, or public key is outdated.'));
315                                 return null;
316                         }
317                 } else
318                         $refresh = false;
319
320         } while($refresh);
321
322         $Release = get_control_file($loclist, 'Filename');
323         // download and check all indexes containing given package types
324         // then complete package list or seek for pkg
325         $Packages = array();
326         foreach($Release as $fname => $parms) {
327                 if ($type && !count(array_intersect(explode(' ', $parms['Type']), $type))) {
328                         unset($Release[$fname]); continue; // no packages of selected type in this index
329                 }
330                 if ($Release[$fname]['Version'] != $repo_auth['branch']) {
331                         display_warning(_('Repository version does not match application version.')); // ?
332                 }
333                 $remoteindex = $repo.'/'.$fname;
334                 $locindex = PKG_CACHE_PATH.'/'.$fname;
335                 $refresh = true;
336                 do{
337                         if (!file_exists($locindex)) { 
338                                 if (!url_copy($remoteindex, $locindex)) {
339                                         display_error(sprintf(_("Cannot download '%s' file." ), $fname));
340                                         return null;
341                                 }
342                                 $refresh = false;
343                         }
344                         if ($parms['SHA1sum'] != sha1_file($locindex)) {        // check subdir index consistency
345                                 if ($refresh) {
346                                         if (!@unlink($locindex)) {
347                                                 display_error(sprintf(_("Cannot delete outdated '%s' file."), $locindex));
348                                                 return null;
349                                         }
350                                 } else {
351                                         display_error(sprintf( _("Security alert: broken index file in repository '%s'. Please inform repository administrator about this issue."),
352                                                 $fname));
353                                         return null;
354                                 }
355                         } else
356                                 $refresh = false;
357                 } while($refresh);
358                 
359                  // scan subdir list and select packages of given type
360                 $pkglist = get_control_file($locindex, 'Package');
361                 foreach($pkglist as $name => $pkg) {
362                         $pkgfullname = $repo.'/'.$parms['Path']."/".$pkg['Filename'].'.pkg';
363                         if (!isset($type) || in_array($pkg['Type'], $type)) {
364                                 if (empty($filter))
365                                         $p = $pkg;
366                                 else {
367                                         foreach($filter as $field => $key) {
368                                                 if (is_numeric($field))
369                                                         $p[$field] = @$pkg[$field];
370                                                 else
371                                                         $p[$key] = @$pkg[$field];
372                                         }
373                                 }
374                                 if ($pkgname == null) {
375                                         $Packages[$outkey ? $outkey : $name] = $p;
376                                 } elseif ($pkgname == $pkg['Package']) {
377                                         //download package to temp directory
378                                         if ($download) {
379                                                 $locname = $target_dir.$pkg['Filename'].'.pkg';
380                                                 if (!url_copy($pkgfullname, $locname)) {
381                                                         display_error(sprintf(_("Cannot download '%s' file." ), $pkgfullname));
382                                                         return null;
383                                                 }
384                                                  // checking sha1 hash is expensive proces, so chekc the package
385                                                  // consistency just before downloading
386                                                 if ($pkg['SHA1sum'] != sha1_file($locname)) {
387                                                         display_error(sprintf( _("Security alert: broken package '%s' in repository. Please inform repository administrator about this issue."),
388                                                                 $pkgfullname));
389                                                         return null;
390                                                 }
391                                         }
392                                         return $p;
393                                 }
394                         }
395                 }
396         }
397
398         return $Packages;
399 }
400
401 function get_package($pkgname, $type = null)
402 {
403         return get_pkg_or_list($type, $pkgname);
404 }
405 /*
406         Returns full name of installed package, or null if package is not installed.
407 */
408 function installed_package($package)
409 {
410         $cache = opendir(PKG_CACHE_PATH);
411
412         while ($file = @readdir($cache)) {
413                 if (!is_dir(PKG_CACHE_PATH.'/'.$file))
414                         continue;
415                 if (strpos($file, $package.'-') === 0)
416                         return $file;
417         }
418         @closedir($cache);
419
420         return null;
421 }
422 /*
423         Remove package from system
424 */
425 function uninstall_package($name)
426 {
427         $name = installed_package($name);
428         if (!$name) return true; // not installed
429         $pkg = new package($name.'.pkg');
430         $pkg->uninstall();
431         if($name) {
432                 flush_dir(PKG_CACHE_PATH.'/'.$name, true);
433                 rmdir(PKG_CACHE_PATH.'/'.$name);
434         }
435         return count($pkg->error)==0;
436 }
437
438 //---------------------------------------------------------------------------------------
439 //
440 //      Return merged list of available and installed languages in inform of local 
441 // configuration array supplemented with installed versions information.
442 //
443 function get_languages_list()
444 {
445         global $installed_languages;
446         
447         $pkgs = get_pkg_or_list('language', null, array(
448                                 'Package' => 'package',
449                                 'Version' => 'available',
450                                 'Name' => 'name',
451                                 'Language' => 'code',
452                                 'Encoding' => 'encoding',
453                                 'RTLDir' => 'rtl',
454                                 'Description' => 'Descr',
455                                 'InstallPath' => 'path'
456                         ));
457
458         // add/update languages already installed
459         // 
460         foreach($installed_languages as $id => $l) {
461                 $list = array_search_keys($l['code'], $pkgs, 'code');   // get all packages with this code
462                 foreach ($list as $name) {
463                         if ($l['encoding'] == $pkgs[$name]['encoding']) {       // if the same encoding
464                                 $pkgs[$name]['version'] = @$l['version'];               // set installed version
465                                 $pkgs[$name]['local_id'] = $id;         // index in installed_languages
466                                 continue 2;
467                         }
468                 }
469                 $l['local_id'] = $id;
470                 if (!isset($l['package']) || $l['package'] == '' || !isset($pkgs[$l['package']]))
471                         $pkgs[] = $l;
472                 else
473                         $pkgs[$l['package']] = array_merge($pkgs[$l['package']], $l);
474         }
475         if ($pkgs)
476                 ksort($pkgs);
477         return $pkgs;
478 }
479 //---------------------------------------------------------------------------------------
480 //
481 //      Return merged list of available and installed extensions as a local 
482 // configuration array supplemented with installed versions information.
483 //
484 function get_extensions_list($type = null)
485 {
486         global $path_to_root;
487
488         if (isset($type) || !is_array($type)) {
489                 $type = array($type);
490         }
491
492         $pkgs = get_pkg_or_list($type, null, array(
493                                 'Package' => 'package',
494                                 'Version' => 'available',
495                                 'Name' => 'name',
496                                 'Description' => 'Descr',
497                                 'Type' => 'type',
498                                 'DefaultStatus' => 'active',
499 //                              'MenuTabs' => 'tabs',
500 //                              'MenuEntries' => 'entries',
501                                 'Encoding' => 'encoding',
502 //                              'AccessExtensions' => 'acc_file',
503                                 'InstallPath' => 'path'
504                         ));
505
506         // lookup for local extensions
507         $path = $path_to_root.'/modules/';
508         $loc = array();
509         $moddir = opendir($path);
510
511         while(false != ($fname = readdir($moddir)))
512         {
513                 if(!in_array($fname, array('.','..','CVS','_cache')) && is_dir($path.$fname))
514                 {
515                         if (!isset($pkgs[$fname]))
516                                 $pkgs[$fname] = array(
517                                         'package' => $fname,
518                                         'name' => $fname,
519                                         'version' => '',
520                                         'available' => '',
521                                         'type' => 'extension',
522                                         'path' => 'modules/'.$fname,
523                                         'active' => false
524                                         );
525                 }
526         }
527
528         // add/update extensions already installed
529         // 
530         $installed = get_company_extensions();
531         foreach($installed as $extno => $ext) {
532                 if (!in_array($ext['type'], $type)) continue;
533                 $ext['local_id'] = $extno;
534 //              if (!isset($pkgs[$ext['package']]) || $ext['package'] == '')
535 //                      $pkgs[] = $ext;
536 //              else
537                         $pkgs[$ext['package']] = array_merge($pkgs[$ext['package']], $ext);
538         }
539         if ($pkgs)
540                 ksort($pkgs);
541         return $pkgs;
542 }
543 //
544 // Return merged list of available and installed extensions as a local
545 // configuration array supplemented with installed versions information.
546 //
547 function get_themes_list()
548 {
549         $pkgs = get_pkg_or_list('theme', null, array(
550                                 'Package' => 'package',
551                                 'Version' => 'available',
552                                 'Name' => 'name',
553                                 'Description' => 'Descr'
554                         ));
555
556         // add/update extensions already installed
557         // 
558         $local = get_company_extensions();
559         
560         foreach($local as $extno => $ext) {
561                 if (isset($pkgs[@$ext['package']])) {
562                         $ext['local_id'] = $extno;
563                         $pkgs[$ext['package']] = array_merge($pkgs[$ext['package']], $ext);
564                 }
565         }
566         // TODO: Add other themes from themes directory
567         if ($pkgs)
568                 ksort($pkgs);
569         return $pkgs;
570 }
571 //---------------------------------------------------------------------------------------
572 //
573 //      Return merged list of available and installed COAs as a local 
574 // configuration array supplemented with installed versions information.
575 //
576 function get_charts_list()
577 {
578         $pkgs = get_pkg_or_list('chart', null, array(
579                                 'Package' => 'package',
580                                 'Version' => 'available',
581                                 'Name' => 'name',
582                                 'Description' => 'Descr',
583                                 'Type' => 'type',
584                                 'InstallPath' => 'path',
585                                 'Encoding' => 'encoding',
586                                 'SqlScript' => 'sql'
587                         ));
588
589         // add/update default charts
590         // 
591         $local = get_company_extensions();
592
593         foreach($local as $extno => $ext) {
594                 if ($ext['type'] != 'chart') continue;
595                 $ext['local_id'] = $extno;
596                 if (!isset($pkgs[$ext['package']]) || $ext['package'] == '')
597                         $pkgs[] = $ext;
598                 else
599                         $pkgs[$ext['package']] = array_merge($pkgs[$ext['package']], $ext);
600         }
601         if ($pkgs)
602                 ksort($pkgs);
603         return $pkgs;
604 }
605 //---------------------------------------------------------------------------------------------
606 //      Install/update package from repository
607 //
608 function install_language($pkg_name)
609 {
610         global $path_to_root, $installed_languages, $Ajax;
611         
612         $pkg = get_pkg_or_list('language', $pkg_name);
613
614         if ($pkg) {
615                 $i = array_search_key($pkg['Language'], $installed_languages, 'code');
616                 if ($i === null)
617                         $i = count($installed_languages);
618                 else {  // remove another already installed package for this language 
619                         $old_pkg = @$installed_languages[$i]['package'];
620                         if ($old_pkg && ($pkg['Package'] != $old_pkg))
621                                 uninstall_package($old_pkg);
622                 }
623
624                 $package = new package("$path_to_root/tmp/".$pkg['Filename'].'.pkg');
625                 if ($package->install()) {
626                         $lang = array(
627                                 'name' => $pkg['Name'],
628                                 'package' => $pkg['Package'],
629                                 'code' => $pkg['Language'],
630                                 'encoding' => $pkg['Encoding'],
631                                 'version' => $pkg['Version'],
632                                 'path' => $pkg['InstallPath']
633                         );
634                         if ($pkg['RTLDir']=='yes')
635                                 $lang['rtl'] = true;
636                         $installed_languages[$i] = $lang;
637                         write_lang($installed_languages);
638                         unlink("$path_to_root/tmp/".$pkg['Filename'].'.pkg');
639                         $Ajax->activate('lang_tbl');
640                 } else {
641                         display_error(implode('<br>', $package->error));
642                         return false;
643                 }
644         } else {
645                 display_error(sprintf(_("Package '%s' not found."), $pkg_name));
646                 return false;
647         }
648         return true;
649 }
650 //---------------------------------------------------------------------------------------------
651 //      Install/update extension or theme package from repository
652 //
653 function install_extension($pkg_name)
654 {
655         global $path_to_root, $installed_extensions, $next_extension_id, $Ajax, $db_connections;
656         
657         $pkg = get_pkg_or_list(array('extension', 'theme', 'chart'), $pkg_name);
658         if ($pkg) {
659                 $package = new package("$path_to_root/tmp/".$pkg['Filename'].'.pkg');
660                 $local_exts = get_company_extensions();
661                 if ($package->install()) {
662                         $ext_id = array_search_key($pkg['Package'], $local_exts, 'package');
663                         if ($ext_id === null)
664                                 $ext_id = $next_extension_id++;
665                         else {  // remove another already installed package for this language 
666                                 $old_pkg = $installed_extensions[$ext_id]['package'];
667                                 if ($old_pkg)
668                                         uninstall_package($old_pkg);
669                         }
670                         $ext = array(
671                                 'name' => $pkg['Name'],
672                                 'package' => $pkg['Package'],
673                                 'version' => $pkg['Version'],
674                                 'type' => $pkg['Type'],
675                                 'active' => @$pkg['DefaultStatus'] == 'active' ? true : false,
676                                 'path' => $pkg['InstallPath'],
677                         );
678                         if (isset($pkg['SqlScript']))
679                                 $ext['sql'] = $pkg['SqlScript'];
680
681                         $local_exts[$ext_id] = $ext;
682                         $ret = update_extensions($local_exts);
683
684                         if (($ext['active'] == true) && file_exists($path_to_root.'/'.$ext['path'].'/hooks.php'))
685                         {
686                                 // we need to include the new hooks file to activate extension
687                                 include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
688                                 foreach($db_connections as $comp => $db)
689                                         activate_hooks($ext['package'], $comp);
690                         }
691
692                         unlink("$path_to_root/tmp/".$pkg['Filename'].'.pkg');
693                         $Ajax->activate('ext_tbl');
694                         return $ret;
695                 } else {
696                         display_error(implode('<br>', $package->error));
697                         return false;
698                 }
699         } else {
700                 display_error(sprintf(_("Package '%s' not found."), $pkg_name));
701                 return false;
702         }
703         return true;
704 }
705 /*
706         Returns true if newer package version is available
707 */
708 function check_pkg_upgrade($current, $available)
709 {
710         preg_match_all('/[\d]+/', $available, $aver);
711         if (!count($aver[0]))
712                 return false;
713         preg_match_all('/[\d]+/', $current, $cver);
714         if (!count($cver[0]))
715                 return true;
716         foreach($aver[0] as $n => $ver)
717                 if ($ver>@$cver[0][$n]) 
718                         return true;
719         return false;
720 }
721
722 //
723 //      Returns package info from index file
724 //
725 function get_package_info($pkg, $type=null, $filter=array(), $outkey=null, $download=true) {
726         return get_pkg_or_list($type, $pkg, $filter, null, false);
727 }
728
729 /*
730         Check basic extension source compatibility.
731 */
732 function check_src_ext_version($ext_v)
733 {
734         global $src_version;
735
736         $compat_levels = 2;     // current policy is keeping compatibility on major version level.
737         $app = explode('.', substr($src_version, 0, strspn($src_version, "0123456789.")));
738         $pkg = explode('.', substr($ext_v, 0, strspn($ext_v, "0123456789.")));
739
740         for ($i=0; $i < min($compat_levels, count($app)); $i++)
741                 if ($pkg[$i] < $app[$i])
742                         return false;
743
744         return true;
745 }
746