1<?php
2// +-----------------------------------------------------------------------+
3// | This file is part of Piwigo.                                          |
4// |                                                                       |
5// | For copyright and license information, please view the COPYING.txt    |
6// | file that was distributed with this source code.                      |
7// +-----------------------------------------------------------------------+
8
9if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
10
11class updates
12{
13  var $types = array();
14  var $plugins;
15  var $themes;
16  var $languages;
17  var $missing = array();
18  var $default_plugins = array();
19  var $default_themes = array();
20  var $default_languages = array();
21  var $merged_extensions = array();
22  var $merged_extension_url = 'http://piwigo.org/download/merged_extensions.txt';
23
24  function __construct($page='updates')
25  {
26    $this->types = array('plugins', 'themes', 'languages');
27
28    if (in_array($page, $this->types))
29    {
30      $this->types = array($page);
31    }
32    $this->default_themes = array('modus', 'elegant', 'smartpocket');
33    $this->default_plugins = array('AdminTools', 'TakeATour', 'language_switch', 'LocalFilesEditor');
34
35    foreach ($this->types as $type)
36    {
37      include_once(PHPWG_ROOT_PATH.'admin/include/'.$type.'.class.php');
38      $this->$type = new $type();
39    }
40  }
41
42  static function check_piwigo_upgrade()
43  {
44    $_SESSION['need_update'.PHPWG_VERSION] = null;
45
46    if (preg_match('/(\d+\.\d+)\.(\d+)/', PHPWG_VERSION, $matches)
47      and @fetchRemote(PHPWG_URL.'/download/all_versions.php?rand='.md5(uniqid(rand(), true)), $result))
48    {
49      $all_versions = @explode("\n", $result);
50      $new_version = trim($all_versions[0]);
51      $_SESSION['need_update'.PHPWG_VERSION] = version_compare(PHPWG_VERSION, $new_version, '<');
52    }
53  }
54
55  /**
56   * finds new versions of Piwigo on Piwigo.org.
57   *
58   * @since 2.9
59   * @return array (
60   *   'piwigo.org-checked' => has piwigo.org been checked?,
61   *   'is_dev' => are we on a dev version?,
62   *   'minor_version' => new minor version available,
63   *   'major_version' => new major version available,
64   * )
65   */
66  function get_piwigo_new_versions()
67  {
68    $new_versions = array(
69      'piwigo.org-checked' => false,
70      'is_dev' => true,
71      );
72
73    if (preg_match('/^(\d+\.\d+)\.(\d+)$/', PHPWG_VERSION))
74    {
75      $new_versions['is_dev'] = false;
76      $actual_branch = get_branch_from_version(PHPWG_VERSION);
77
78      $url = PHPWG_URL.'/download/all_versions.php';
79      $url.= '?rand='.md5(uniqid(rand(), true)); // Avoid server cache
80      $url.= '&show_requirements';
81
82      if (@fetchRemote($url, $result)
83          and $all_versions = @explode("\n", $result)
84          and is_array($all_versions))
85      {
86        $new_versions['piwigo.org-checked'] = true;
87        $last_version = trim($all_versions[0]);
88        list($last_version_number, $last_version_php) = explode('/', trim($all_versions[0]));
89
90        if (version_compare(PHPWG_VERSION, $last_version_number, '<'))
91        {
92          $last_branch = get_branch_from_version($last_version_number);
93
94          if ($last_branch == $actual_branch)
95          {
96            $new_versions['minor'] = $last_version_number;
97            $new_versions['minor_php'] = $last_version_php;
98          }
99          else
100          {
101            $new_versions['major'] = $last_version_number;
102            $new_versions['major_php'] = $last_version_php;
103
104            // Check if new version exists in same branch
105            foreach ($all_versions as $version)
106            {
107              list($version_number, $version_php) = explode('/', trim($version));
108              $branch = get_branch_from_version($version_number);
109
110              if ($branch == $actual_branch)
111              {
112                if (version_compare(PHPWG_VERSION, $version_number, '<'))
113                {
114                  $new_versions['minor'] = $version_number;
115                  $new_versions['minor_php'] = $version_php;
116                }
117                break;
118              }
119            }
120          }
121        }
122      }
123    }
124    return $new_versions;
125  }
126
127  /**
128   * Checks for new versions of Piwigo. Notify webmasters if new versions are available, but not too often, see
129   * $conf['update_notify_reminder_period'] parameter.
130   *
131   * @since 2.9
132   */
133  function notify_piwigo_new_versions()
134  {
135    global $conf;
136
137    $new_versions = $this->get_piwigo_new_versions();
138    conf_update_param('update_notify_last_check', date('c'));
139
140    if ($new_versions['is_dev'])
141    {
142      return;
143    }
144
145    $new_versions_string = join(
146      ' & ',
147      array_intersect_key(
148        $new_versions,
149        array_fill_keys(array('minor', 'major'), 1)
150        )
151      );
152
153    if (empty($new_versions_string))
154    {
155      return;
156    }
157
158    // In which case should we notify?
159    // 1. never notified
160    // 2. new versions
161    // 3. no new versions but reminder needed
162
163    $notify = false;
164    if (!isset($conf['update_notify_last_notification']))
165    {
166      $notify = true;
167    }
168    else
169    {
170      $conf['update_notify_last_notification'] = safe_unserialize($conf['update_notify_last_notification']);
171      $last_notification = $conf['update_notify_last_notification']['notified_on'];
172
173      if ($new_versions_string != $conf['update_notify_last_notification']['version'])
174      {
175        $notify = true;
176      }
177      elseif (
178        $conf['update_notify_reminder_period'] > 0
179        and strtotime($last_notification) < strtotime($conf['update_notify_reminder_period'].' seconds ago')
180        )
181      {
182        $notify = true;
183      }
184    }
185
186    if ($notify)
187    {
188      // send email
189      include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
190
191      switch_lang_to(get_default_language());
192
193      $content = l10n('Hello,');
194      $content.= "\n\n".l10n(
195        'Time has come to update your Piwigo with version %s, go to %s',
196        $new_versions_string,
197        get_absolute_root_url().'admin.php?page=updates'
198        );
199      $content.= "\n\n".l10n('It only takes a few clicks.');
200      $content.= "\n\n".l10n('Running on an up-to-date Piwigo is important for security.');
201
202      pwg_mail_admins(
203        array(
204          'subject' => l10n('Piwigo %s is available, please update', $new_versions_string),
205          'content' => $content,
206          'content_format' => 'text/plain',
207          ),
208        array(
209          'filename' => 'notification_admin',
210          ),
211        false, // do not exclude current user
212        true // only webmasters
213        );
214
215      switch_lang_back();
216
217      // save notify
218      conf_update_param(
219        'update_notify_last_notification',
220        array(
221          'version' => $new_versions_string,
222          'notified_on' => date('c'),
223          )
224        );
225    }
226  }
227
228  function get_server_extensions($version=PHPWG_VERSION)
229  {
230    global $user;
231
232    $get_data = array(
233      'format' => 'php',
234    );
235
236    // Retrieve PEM versions
237    $versions_to_check = array();
238    $url = PEM_URL . '/api/get_version_list.php';
239    if (fetchRemote($url, $result, $get_data) and $pem_versions = @unserialize($result))
240    {
241      if (!preg_match('/^\d+\.\d+\.\d+$/', $version))
242      {
243        $version = $pem_versions[0]['name'];
244      }
245      $branch = get_branch_from_version($version);
246      foreach ($pem_versions as $pem_version)
247      {
248        if (strpos($pem_version['name'], $branch) === 0)
249        {
250          $versions_to_check[] = $pem_version['id'];
251        }
252      }
253    }
254    if (empty($versions_to_check))
255    {
256      return false;
257    }
258
259    // Extensions to check
260    $ext_to_check = array();
261    foreach ($this->types as $type)
262    {
263      $fs = 'fs_'.$type;
264      foreach ($this->$type->$fs as $ext)
265      {
266        if (isset($ext['extension']))
267        {
268          $ext_to_check[$ext['extension']] = $type;
269        }
270      }
271    }
272
273    // Retrieve PEM plugins infos
274    $url = PEM_URL . '/api/get_revision_list.php';
275    $get_data = array_merge($get_data, array(
276      'last_revision_only' => 'true',
277      'version' => implode(',', $versions_to_check),
278      'lang' => substr($user['language'], 0, 2),
279      'get_nb_downloads' => 'true',
280      )
281    );
282
283    $post_data = array();
284    if (!empty($ext_to_check))
285    {
286      $post_data['extension_include'] = implode(',', array_keys($ext_to_check));
287    }
288
289    if (fetchRemote($url, $result, $get_data, $post_data))
290    {
291      $pem_exts = @unserialize($result);
292      if (!is_array($pem_exts))
293      {
294        return false;
295      }
296
297      $servers = array();
298
299      foreach ($pem_exts as $ext)
300      {
301        if (isset($ext_to_check[$ext['extension_id']]))
302        {
303          $type = $ext_to_check[$ext['extension_id']];
304
305          if (!isset($servers[$type]))
306          {
307            $servers[$type] = array();
308          }
309
310          $servers[$type][ $ext['extension_id'] ] = $ext;
311
312          unset($ext_to_check[$ext['extension_id']]);
313        }
314      }
315
316      foreach ($servers as $server_type => $extension_list)
317      {
318        $server_string = 'server_'.$server_type;
319
320        $this->$server_type->$server_string = $extension_list;
321      }
322
323      $this->check_missing_extensions($ext_to_check);
324      return true;
325    }
326    return false;
327  }
328
329  // Check all extensions upgrades
330  function check_extensions()
331  {
332    global $conf;
333
334    if (!$this->get_server_extensions())
335    {
336      return false;
337    }
338
339    $_SESSION['extensions_need_update'] = array();
340
341    foreach ($this->types as $type)
342    {
343      $fs = 'fs_'.$type;
344      $server = 'server_'.$type;
345      $server_ext = $this->$type->$server;
346      $fs_ext = $this->$type->$fs;
347
348      $ignore_list = array();
349      $need_upgrade = array();
350
351      foreach($fs_ext as $ext_id => $fs_ext)
352      {
353        if (isset($fs_ext['extension']) and isset($server_ext[$fs_ext['extension']]))
354        {
355          $ext_info = $server_ext[$fs_ext['extension']];
356
357          if (!safe_version_compare($fs_ext['version'], $ext_info['revision_name'], '>='))
358          {
359            if (in_array($ext_id, $conf['updates_ignored'][$type]))
360            {
361              $ignore_list[] = $ext_id;
362            }
363            else
364            {
365              $_SESSION['extensions_need_update'][$type][$ext_id] = $ext_info['revision_name'];
366            }
367          }
368        }
369      }
370      $conf['updates_ignored'][$type] = $ignore_list;
371    }
372    conf_update_param('updates_ignored', pwg_db_real_escape_string(serialize($conf['updates_ignored'])));
373  }
374
375  // Check if extension have been upgraded since last check
376  function check_updated_extensions()
377  {
378    foreach ($this->types as $type)
379    {
380      if (!empty($_SESSION['extensions_need_update'][$type]))
381      {
382        $fs = 'fs_'.$type;
383        foreach($this->$type->$fs as $ext_id => $fs_ext)
384        {
385          if (isset($_SESSION['extensions_need_update'][$type][$ext_id])
386            and safe_version_compare($fs_ext['version'], $_SESSION['extensions_need_update'][$type][$ext_id], '>='))
387          {
388            // Extension have been upgraded
389            $this->check_extensions();
390            break;
391          }
392        }
393      }
394    }
395  }
396
397  function check_missing_extensions($missing)
398  {
399    foreach ($missing as $id => $type)
400    {
401      $fs = 'fs_'.$type;
402      $default = 'default_'.$type;
403      foreach ($this->$type->$fs as $ext_id => $ext)
404      {
405        if (isset($ext['extension']) and $id == $ext['extension']
406          and !in_array($ext_id, $this->$default)
407          and !in_array($ext['extension'], $this->merged_extensions))
408        {
409          $this->missing[$type][] = $ext;
410          break;
411        }
412      }
413    }
414  }
415
416  function get_merged_extensions($version)
417  {
418    if (fetchRemote($this->merged_extension_url, $result))
419    {
420      $rows = explode("\n", $result);
421      foreach ($rows as $row)
422      {
423        if (preg_match('/^(\d+\.\d+): *(.*)$/', $row, $match))
424        {
425          if (version_compare($version, $match[1], '>='))
426          {
427            $extensions = explode(',', trim($match[2]));
428            $this->merged_extensions = array_merge($this->merged_extensions, $extensions);
429          }
430        }
431      }
432    }
433  }
434
435  static function process_obsolete_list($file)
436  {
437    if (file_exists(PHPWG_ROOT_PATH.$file)
438      and $old_files = file(PHPWG_ROOT_PATH.$file, FILE_IGNORE_NEW_LINES)
439      and !empty($old_files))
440    {
441      $old_files[] = $file;
442      foreach($old_files as $old_file)
443      {
444        $path = PHPWG_ROOT_PATH.$old_file;
445        if (is_file($path))
446        {
447          @unlink($path);
448        }
449        elseif (is_dir($path))
450        {
451          deltree($path, PHPWG_ROOT_PATH.'_trash');
452        }
453      }
454    }
455  }
456
457  static function upgrade_to($upgrade_to, &$step, $check_current_version=true)
458  {
459    global $page, $conf, $template;
460
461    if ($check_current_version and !version_compare($upgrade_to, PHPWG_VERSION, '>'))
462    {
463      redirect(get_root_url().'admin.php?page=plugin-'.basename(dirname(__FILE__)));
464    }
465
466    if ($step == 2)
467    {
468      $code = get_branch_from_version(PHPWG_VERSION).'.x_to_'.$upgrade_to;
469      $dl_code = str_replace(array('.', '_'), '', $code);
470      $remove_path = $code;
471      $obsolete_list = 'obsolete.list';
472    }
473    else
474    {
475      $code = $upgrade_to;
476      $dl_code = $code;
477      $remove_path = version_compare($code, '2.0.8', '>=') ? 'piwigo' : 'piwigo-'.$code;
478      $obsolete_list = PHPWG_ROOT_PATH.'install/obsolete.list';
479    }
480
481    if (empty($page['errors']))
482    {
483      $path = PHPWG_ROOT_PATH.$conf['data_location'].'update';
484      $filename = $path.'/'.$code.'.zip';
485      @mkgetdir($path);
486
487      $chunk_num = 0;
488      $end = false;
489      $zip = @fopen($filename, 'w');
490
491      while (!$end)
492      {
493        $chunk_num++;
494        if (@fetchRemote(PHPWG_URL.'/download/dlcounter.php?code='.$dl_code.'&chunk_num='.$chunk_num, $result)
495          and $input = @unserialize($result))
496        {
497          if (0 == $input['remaining'])
498          {
499            $end = true;
500          }
501          @fwrite($zip, base64_decode($input['data']));
502        }
503        else
504        {
505          $end = true;
506        }
507      }
508      @fclose($zip);
509
510      if (@filesize($filename))
511      {
512        $zip = new PclZip($filename);
513        if ($result = $zip->extract(PCLZIP_OPT_PATH, PHPWG_ROOT_PATH,
514                                    PCLZIP_OPT_REMOVE_PATH, $remove_path,
515                                    PCLZIP_OPT_SET_CHMOD, 0755,
516                                    PCLZIP_OPT_REPLACE_NEWER))
517        {
518          //Check if all files were extracted
519          $error = '';
520          foreach($result as $extract)
521          {
522            if (!in_array($extract['status'], array('ok', 'filtered', 'already_a_directory')))
523            {
524              // Try to change chmod and extract
525              if (@chmod(PHPWG_ROOT_PATH.$extract['filename'], 0777)
526                and ($res = $zip->extract(PCLZIP_OPT_BY_NAME, $remove_path.'/'.$extract['filename'],
527                                          PCLZIP_OPT_PATH, PHPWG_ROOT_PATH,
528                                          PCLZIP_OPT_REMOVE_PATH, $remove_path,
529                                          PCLZIP_OPT_SET_CHMOD, 0755,
530                                          PCLZIP_OPT_REPLACE_NEWER))
531                and isset($res[0]['status'])
532                and $res[0]['status'] == 'ok')
533              {
534                continue;
535              }
536              else
537              {
538                $error .= $extract['filename'].': '.$extract['status']."\n";
539              }
540            }
541          }
542
543          if (empty($error))
544          {
545            self::process_obsolete_list($obsolete_list);
546            deltree(PHPWG_ROOT_PATH.$conf['data_location'].'update');
547            invalidate_user_cache(true);
548            $template->delete_compiled_templates();
549            if ($step == 2)
550            {
551              $page['infos'][] = l10n('Update Complete');
552              $page['infos'][] = $upgrade_to;
553              $step = -1;
554            }
555            else
556            {
557              redirect(PHPWG_ROOT_PATH.'upgrade.php?now=');
558            }
559          }
560          else
561          {
562            file_put_contents(PHPWG_ROOT_PATH.$conf['data_location'].'update/log_error.txt', $error);
563
564            $page['errors'][] = l10n(
565              'An error has occured during extract. Please check files permissions of your piwigo installation.<br><a href="%s">Click here to show log error</a>.',
566              get_root_url().$conf['data_location'].'update/log_error.txt'
567              );
568          }
569        }
570        else
571        {
572          deltree(PHPWG_ROOT_PATH.$conf['data_location'].'update');
573          $page['errors'][] = l10n('An error has occured during upgrade.');
574        }
575      }
576      else
577      {
578        $page['errors'][] = l10n('Piwigo cannot retrieve upgrade file from server');
579      }
580    }
581  }
582}
583
584?>