source: trunk/admin/include/plugins.class.php @ 10594

Last change on this file since 10594 was 10594, checked in by patdenice, 13 years ago

Change tabsheet place for plugins, themes and languages.
Plugins, themes and languages use same update page.

  • Property svn:eol-style set to LF
File size: 19.3 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2011 Piwigo Team                  http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24class plugins
25{
26  var $fs_plugins = array();
27  var $db_plugins_by_id = array();
28  var $server_plugins = array();
29  var $default_plugins = array('LocalFilesEditor', 'language_switch', 'c13y_upgrade', 'admin_multi_view');
30
31  /**
32   * Initialize $fs_plugins and $db_plugins_by_id
33  */
34  function plugins()
35  {
36    $this->get_fs_plugins();
37
38    foreach (get_db_plugins() as $db_plugin)
39    {
40      $this->db_plugins_by_id[$db_plugin['id']] = $db_plugin;
41    }
42  }
43
44 /**
45   * Perform requested actions
46  *  @param string - action
47  * @param string - plugin id
48  * @param array - errors
49  */
50  function perform_action($action, $plugin_id)
51  {
52    if (isset($this->db_plugins_by_id[$plugin_id]))
53    {
54      $crt_db_plugin = $this->db_plugins_by_id[$plugin_id];
55    }
56    $file_to_include = PHPWG_PLUGINS_PATH . $plugin_id . '/maintain.inc.php';
57
58    $errors = array();
59
60    switch ($action)
61    {
62      case 'install':
63        if (!empty($crt_db_plugin) or !isset($this->fs_plugins[$plugin_id]))
64        {
65          break;
66        }
67        if (file_exists($file_to_include))
68        {
69          include_once($file_to_include);
70          if (function_exists('plugin_install'))
71          {
72            plugin_install($plugin_id, $this->fs_plugins[$plugin_id]['version'], $errors);
73          }
74        }
75        if (empty($errors))
76        {
77          $query = '
78INSERT INTO ' . PLUGINS_TABLE . ' (id,version) VALUES (\''
79. $plugin_id . '\',\'' . $this->fs_plugins[$plugin_id]['version'] . '\'
80)';
81          pwg_query($query);
82        }
83        break;
84
85      case 'activate':
86        if (!isset($crt_db_plugin))
87        {
88          $errors = $this->perform_action('install', $plugin_id);
89        }
90        elseif ($crt_db_plugin['state'] == 'active')
91        {
92          break;
93        }
94        if (empty($errors) and file_exists($file_to_include))
95        {
96          include_once($file_to_include);
97          if (function_exists('plugin_activate'))
98          {
99            plugin_activate($plugin_id, $crt_db_plugin['version'], $errors);
100          }
101        }
102        if (empty($errors))
103        {
104          $query = '
105UPDATE ' . PLUGINS_TABLE . '
106SET state=\'active\', version=\''.$this->fs_plugins[$plugin_id]['version'].'\'
107WHERE id=\'' . $plugin_id . '\'';
108          pwg_query($query);
109        }
110        break;
111
112      case 'deactivate':
113        if (!isset($crt_db_plugin) or $crt_db_plugin['state'] != 'active')
114        {
115          break;
116        }
117        $query = '
118UPDATE ' . PLUGINS_TABLE . ' SET state=\'inactive\' WHERE id=\'' . $plugin_id . '\'';
119        pwg_query($query);
120        if (file_exists($file_to_include))
121        {
122          include_once($file_to_include);
123          if (function_exists('plugin_deactivate'))
124          {
125            plugin_deactivate($plugin_id);
126          }
127        }
128        break;
129
130      case 'uninstall':
131        if (!isset($crt_db_plugin))
132        {
133          break;
134        }
135        if ($crt_db_plugin['state'] == 'active')
136        {
137          $this->perform_action('deactivate', $plugin_id);
138        }
139        $query = '
140DELETE FROM ' . PLUGINS_TABLE . ' WHERE id=\'' . $plugin_id . '\'';
141        pwg_query($query);
142        if (file_exists($file_to_include))
143        {
144          include_once($file_to_include);
145          if (function_exists('plugin_uninstall'))
146          {
147            plugin_uninstall($plugin_id);
148          }
149        }
150        break;
151
152      case 'restore':
153        $this->perform_action('uninstall', $plugin_id);
154        unset($this->db_plugins_by_id[$plugin_id]);
155        $errors = $this->perform_action('activate', $plugin_id);
156        break;
157
158      case 'delete':
159        if (!empty($crt_db_plugin))
160        {
161          $this->perform_action('uninstall', $plugin_id);
162        }
163        if (!isset($this->fs_plugins[$plugin_id]))
164        {
165          break;
166        }
167        if (!$this->deltree(PHPWG_PLUGINS_PATH . $plugin_id))
168        {
169          $this->send_to_trash(PHPWG_PLUGINS_PATH . $plugin_id);
170        }
171        break;
172    }
173    return $errors;
174  }
175
176  /**
177  *  Get plugins defined in the plugin directory
178  */ 
179  function get_fs_plugins()
180  {
181    $dir = opendir(PHPWG_PLUGINS_PATH);
182    while ($file = readdir($dir))
183    {
184      if ($file!='.' and $file!='..')
185      {
186        $path = PHPWG_PLUGINS_PATH.$file;
187        if (is_dir($path) and !is_link($path)
188            and preg_match('/^[a-zA-Z0-9-_]+$/', $file )
189            and file_exists($path.'/main.inc.php')
190            )
191        {
192          $plugin = array(
193              'name'=>$file,
194              'version'=>'0',
195              'uri'=>'',
196              'description'=>'',
197              'author'=>'',
198            );
199          $plg_data = implode( '', file($path.'/main.inc.php') );
200
201          if ( preg_match("|Plugin Name: (.*)|", $plg_data, $val) )
202          {
203            $plugin['name'] = trim( $val[1] );
204          }
205          if (preg_match("|Version: (.*)|", $plg_data, $val))
206          {
207            $plugin['version'] = trim($val[1]);
208          }
209          if ( preg_match("|Plugin URI: (.*)|", $plg_data, $val) )
210          {
211            $plugin['uri'] = trim($val[1]);
212          }
213          if ($desc = load_language('description.txt', $path.'/', array('return' => true)))
214          {
215            $plugin['description'] = trim($desc);
216          }
217          elseif ( preg_match("|Description: (.*)|", $plg_data, $val) )
218          {
219            $plugin['description'] = trim($val[1]);
220          }
221          if ( preg_match("|Author: (.*)|", $plg_data, $val) )
222          {
223            $plugin['author'] = trim($val[1]);
224          }
225          if ( preg_match("|Author URI: (.*)|", $plg_data, $val) )
226          {
227            $plugin['author uri'] = trim($val[1]);
228          }
229          if (!empty($plugin['uri']) and strpos($plugin['uri'] , 'extension_view.php?eid='))
230          {
231            list( , $extension) = explode('extension_view.php?eid=', $plugin['uri']);
232            if (is_numeric($extension)) $plugin['extension'] = $extension;
233          }
234          // IMPORTANT SECURITY !
235          $plugin = array_map('htmlspecialchars', $plugin);
236          $this->fs_plugins[$file] = $plugin;
237        }
238      }
239    }
240    closedir($dir);
241  }
242
243  /**
244   * Sort fs_plugins
245   */
246  function sort_fs_plugins($order='name')
247  {
248    switch ($order)
249    {
250      case 'name':
251        uasort($this->fs_plugins, 'name_compare');
252        break;
253      case 'status':
254        $this->sort_plugins_by_state();
255        break;
256      case 'author':
257        uasort($this->fs_plugins, array($this, 'plugin_author_compare'));
258        break;
259      case 'id':
260        uksort($this->fs_plugins, 'strcasecmp');
261        break;
262    }
263  }
264
265  // Retrieve PEM versions
266  function get_versions_to_check($version=PHPWG_VERSION)
267  {
268    $versions_to_check = array();
269    $url = PEM_URL . '/api/get_version_list.php?category=12&format=php';
270    if (fetchRemote($url, $result) and $pem_versions = @unserialize($result))
271    {
272      if (!preg_match('/^\d+\.\d+\.\d+/', $version))
273      {
274        $version = $pem_versions[0]['name'];
275      }
276      $branch = substr($version, 0, strrpos($version, '.'));
277      foreach ($pem_versions as $pem_version)
278      {
279        if (strpos($pem_version['name'], $branch) === 0)
280        {
281          $versions_to_check[] = $pem_version['id'];
282        }
283      }
284    }
285    return $versions_to_check;
286  }
287
288  /**
289   * Retrieve PEM server datas to $server_plugins
290   */
291  function get_server_plugins($new=false)
292  {
293    global $user;
294
295    $versions_to_check = $this->get_versions_to_check();
296    if (empty($versions_to_check))
297    {
298      return false;
299    }
300
301    // Plugins to check
302    $plugins_to_check = array();
303    foreach($this->fs_plugins as $fs_plugin)
304    {
305      if (isset($fs_plugin['extension']))
306      {
307        $plugins_to_check[] = $fs_plugin['extension'];
308      }
309    }
310
311    // Retrieve PEM plugins infos
312    $url = PEM_URL . '/api/get_revision_list.php';
313    $get_data = array(
314      'category_id' => 12,
315      'format' => 'php',
316      'last_revision_only' => 'true',
317      'version' => implode(',', $versions_to_check),
318      'lang' => substr($user['language'], 0, 2),
319      'get_nb_downloads' => 'true',
320    );
321
322    if (!empty($plugins_to_check))
323    {
324      if ($new)
325      {
326        $get_data['extension_exclude'] = implode(',', $plugins_to_check);
327      }
328      else
329      {
330        $get_data['extension_include'] = implode(',', $plugins_to_check);
331      }
332    }
333    if (fetchRemote($url, $result, $get_data))
334    {
335      $pem_plugins = @unserialize($result);
336      if (!is_array($pem_plugins))
337      {
338        return false;
339      }
340      foreach ($pem_plugins as $plugin)
341      {
342        $this->server_plugins[$plugin['extension_id']] = $plugin;
343      }
344      return true;
345    }
346    return false;
347  }
348
349  function get_incompatible_plugins($actualize=false)
350  {
351    if (isset($_SESSION['incompatible_plugins']) and !$actualize
352      and $_SESSION['incompatible_plugins']['~~expire~~'] > time())
353    {
354      return $_SESSION['incompatible_plugins'];
355    }
356
357    $_SESSION['incompatible_plugins'] = array('~~expire~~' => time() + 300);
358
359    $versions_to_check = $this->get_versions_to_check();
360    if (empty($versions_to_check))
361    {
362      return false;
363    }
364
365    // Plugins to check
366    $plugins_to_check = array();
367    foreach($this->fs_plugins as $fs_plugin)
368    {
369      if (isset($fs_plugin['extension']))
370      {
371        $plugins_to_check[] = $fs_plugin['extension'];
372      }
373    }
374
375    // Retrieve PEM plugins infos
376    $url = PEM_URL . '/api/get_revision_list.php';
377    $get_data = array(
378      'category_id' => 12,
379      'format' => 'php',
380      'version' => implode(',', $versions_to_check),
381      'extension_include' => implode(',', $plugins_to_check),
382    );
383
384    if (fetchRemote($url, $result, $get_data))
385    {
386      $pem_plugins = @unserialize($result);
387      if (!is_array($pem_plugins))
388      {
389        return false;
390      }
391
392      $server_plugins = array();
393      foreach ($pem_plugins as $plugin)
394      {
395        if (!isset($server_plugins[$plugin['extension_id']]))
396        {
397          $server_plugins[$plugin['extension_id']] = array();
398        }
399        array_push($server_plugins[$plugin['extension_id']], $plugin['revision_name']);
400      }
401
402      foreach ($this->fs_plugins as $plugin_id => $fs_plugin)
403      {
404        if (isset($fs_plugin['extension'])
405          and !in_array($plugin_id, $this->default_plugins)
406          and $fs_plugin['version'] != 'auto'
407          and (!isset($server_plugins[$fs_plugin['extension']]) or !in_array($fs_plugin['version'], $server_plugins[$fs_plugin['extension']])))
408        {
409          $_SESSION['incompatible_plugins'][$plugin_id] = $fs_plugin['version'];
410        }
411      }
412      return $_SESSION['incompatible_plugins'];
413    }
414    return false;
415  }
416 
417  /**
418   * Sort $server_plugins
419   */
420  function sort_server_plugins($order='date')
421  {
422    switch ($order)
423    {
424      case 'date':
425        krsort($this->server_plugins);
426        break;
427      case 'revision':
428        usort($this->server_plugins, array($this, 'extension_revision_compare'));
429        break;
430      case 'name':
431        uasort($this->server_plugins, array($this, 'extension_name_compare'));
432        break;
433      case 'author':
434        uasort($this->server_plugins, array($this, 'extension_author_compare'));
435        break;
436      case 'downloads':
437        usort($this->server_plugins, array($this, 'extension_downloads_compare'));
438        break;
439    }
440  }
441
442  /**
443   * Extract plugin files from archive
444   * @param string - install or upgrade
445   *  @param string - archive URL
446    * @param string - plugin id or extension id
447   */
448  function extract_plugin_files($action, $revision, $dest)
449  {
450    if ($archive = tempnam( PHPWG_PLUGINS_PATH, 'zip'))
451    {
452      $url = PEM_URL . '/download.php';
453      $get_data = array(
454        'rid' => $revision,
455        'origin' => 'piwigo_'.$action,
456      );
457
458      if ($handle = @fopen($archive, 'wb') and fetchRemote($url, $handle, $get_data))
459      {
460        fclose($handle);
461        include(PHPWG_ROOT_PATH.'admin/include/pclzip.lib.php');
462        $zip = new PclZip($archive);
463        if ($list = $zip->listContent())
464        {
465          foreach ($list as $file)
466          {
467            // we search main.inc.php in archive
468            if (basename($file['filename']) == 'main.inc.php'
469              and (!isset($main_filepath)
470              or strlen($file['filename']) < strlen($main_filepath)))
471            {
472              $main_filepath = $file['filename'];
473            }
474          }
475          if (isset($main_filepath))
476          {
477            $root = dirname($main_filepath); // main.inc.php path in archive
478            if ($action == 'upgrade')
479            {
480              $extract_path = PHPWG_PLUGINS_PATH . $dest;
481            }
482            else
483            {
484              $extract_path = PHPWG_PLUGINS_PATH
485                  . ($root == '.' ? 'extension_' . $dest : basename($root));
486            }
487            if($result = $zip->extract(PCLZIP_OPT_PATH, $extract_path,
488                                       PCLZIP_OPT_REMOVE_PATH, $root,
489                                       PCLZIP_OPT_REPLACE_NEWER))
490            {
491              foreach ($result as $file)
492              {
493                if ($file['stored_filename'] == $main_filepath)
494                {
495                  $status = $file['status'];
496                  break;
497                }
498              }
499              if (file_exists($extract_path.'/obsolete.list')
500                and $old_files = file($extract_path.'/obsolete.list', FILE_IGNORE_NEW_LINES)
501                and !empty($old_files))
502              {
503                array_push($old_files, 'obsolete.list');
504                foreach($old_files as $old_file)
505                {
506                  $path = $extract_path.'/'.$old_file;
507                  if (is_file($path))
508                  {
509                    @unlink($path);
510                  }
511                  elseif (is_dir($path))
512                  {
513                    if (!$this->deltree($path))
514                    {
515                      $this->send_to_trash($path);
516                    }
517                  }
518                }
519              }
520            }
521            else $status = 'extract_error';
522          }
523          else $status = 'archive_error';
524        }
525        else $status = 'archive_error';
526      }
527      else $status = 'dl_archive_error';
528    }
529    else $status = 'temp_path_error';
530
531    @unlink($archive);
532    return $status;
533  }
534
535  function get_merged_extensions($version=PHPWG_VERSION)
536  {
537    if (isset($_SESSION['merged_extensions']) and $_SESSION['merged_extensions']['~~expire~~'] > time())
538    {
539      return $_SESSION['merged_extensions'];
540    }
541
542    $_SESSION['merged_extensions'] = array('~~expire~~' => time() + 600);
543
544    if (fetchRemote(PHPWG_URL.'/download/merged_extensions.txt', $result))
545    {
546      $rows = explode("\n", $result);
547      foreach ($rows as $row)
548      {
549        if (preg_match('/^(\d+\.\d+): *(.*)$/', $row, $match))
550        {
551          if (version_compare($version, $match[1], '>='))
552          {
553            $extensions = explode(',', trim($match[2]));
554            $_SESSION['merged_extensions'] = array_merge($_SESSION['merged_extensions'], $extensions);
555          }
556        }
557      }
558    }
559
560    return $_SESSION['merged_extensions'];
561  }
562 
563  /**
564   * delete $path directory
565   * @param string - path
566   */
567  function deltree($path)
568  {
569    if (is_dir($path))
570    {
571      $fh = opendir($path);
572      while ($file = readdir($fh))
573      {
574        if ($file != '.' and $file != '..')
575        {
576          $pathfile = $path . '/' . $file;
577          if (is_dir($pathfile))
578          {
579            $this->deltree($pathfile);
580          }
581          else
582          {
583            @unlink($pathfile);
584          }
585        }
586      }
587      closedir($fh);
588      return @rmdir($path);
589    }
590  }
591
592  /**
593   * send $path to trash directory
594   * @param string - path
595   */
596  function send_to_trash($path)
597  {
598    $trash_path = PHPWG_PLUGINS_PATH . 'trash';
599    if (!is_dir($trash_path))
600    {
601      @mkdir($trash_path);
602      $file = @fopen($trash_path . '/.htaccess', 'w');
603      @fwrite($file, 'deny from all');
604      @fclose($file);
605    }
606    while ($r = $trash_path . '/' . md5(uniqid(rand(), true)))
607    {
608      if (!is_dir($r))
609      {
610        @rename($path, $r);
611        break;
612      }
613    }
614  }
615
616  /**
617   * Sort functions
618   */
619  function plugin_version_compare($a, $b)
620  {
621    if (strtolower($a) == 'auto') return false;
622
623    $pattern = array('/([a-z])/ei', '/\.+/', '/\.\Z|\A\./');
624    $replacement = array( "'.'.intval('\\1', 36).'.'", '.', '');
625
626    $array = preg_replace($pattern, $replacement, array($a, $b));
627
628    return version_compare($array[0], $array[1], '>=');
629  }
630
631  function extension_revision_compare($a, $b)
632  {
633    if ($a['revision_date'] < $b['revision_date']) return 1;
634    else return -1;
635  }
636
637  function extension_name_compare($a, $b)
638  {
639    return strcmp(strtolower($a['extension_name']), strtolower($b['extension_name']));
640  }
641
642  function extension_author_compare($a, $b)
643  {
644    $r = strcasecmp($a['author_name'], $b['author_name']);
645    if ($r == 0) return $this->extension_name_compare($a, $b);
646    else return $r;
647  }
648
649  function plugin_author_compare($a, $b)
650  {
651    $r = strcasecmp($a['author'], $b['author']);
652    if ($r == 0) return name_compare($a, $b);
653    else return $r;
654  }
655
656  function extension_downloads_compare($a, $b)
657  {
658    if ($a['extension_nb_downloads'] < $b['extension_nb_downloads']) return 1;
659    else return -1;
660  }
661
662  function sort_plugins_by_state()
663  {
664    uasort($this->fs_plugins, 'name_compare');
665
666    $active_plugins = array();
667    $inactive_plugins = array();
668    $not_installed = array();
669
670    foreach($this->fs_plugins as $plugin_id => $plugin)
671    {
672      if (isset($this->db_plugins_by_id[$plugin_id]))
673      {
674        $this->db_plugins_by_id[$plugin_id]['state'] == 'active' ?
675          $active_plugins[$plugin_id] = $plugin : $inactive_plugins[$plugin_id] = $plugin;
676      }
677      else
678      {
679        $not_installed[$plugin_id] = $plugin;
680      }
681    }
682    $this->fs_plugins = $active_plugins + $inactive_plugins + $not_installed;
683  }
684}
685?>
Note: See TracBrowser for help on using the repository browser.