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

Last change on this file since 5143 was 5143, checked in by plg, 14 years ago

feature 1507: ability to install themes directly from piwigo.org/ext.

The theme manager backend for installation is the plugin manager backend,
because they are very close.

thanks to feature:1502, installing a theme and installing a plugin are very
similare operations. The only differences are the installation directory and
the main file to find in the archive.

This is only the very first step, the "add new theme" screen needs many
improvements, to begin with display style.

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