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

Last change on this file since 19703 was 19703, checked in by plg, 11 years ago

update Piwigo headers to 2013 (the end of the world didn't occur as expected on r12922)

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