source: branches/2.2/admin/include/plugins.class.php @ 10131

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

merge r10098,r10100,r10101,r10113,r10128,r10129 from trunk to branch 2.2
feature:2250
Incompatible plugins and obsolete plugins may not be activated.
Deactivate and uninstall obsolete plugins.
Add warning icon and tiptip
Add languages keys.
Add expire parameter.
Clean code.
Remove useless query for delete.

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