source: trunk/admin/include/themes.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)

File size: 20.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 themes
25{
26  var $fs_themes = array();
27  var $db_themes_by_id = array();
28  var $server_themes = array();
29
30  /**
31   * Initialize $fs_themes and $db_themes_by_id
32  */
33  function themes()
34  {
35    $this->get_fs_themes();
36
37    foreach ($this->get_db_themes() as $db_theme)
38    {
39      $this->db_themes_by_id[$db_theme['id']] = $db_theme;
40    }
41  }
42
43  /**
44   * Perform requested actions
45   * @param string - action
46   * @param string - theme id
47   * @param array - errors
48   */
49  function perform_action($action, $theme_id)
50  {
51    global $conf;
52
53    if (isset($this->db_themes_by_id[$theme_id]))
54    {
55      $crt_db_theme = $this->db_themes_by_id[$theme_id];
56    }
57
58    $file_to_include = PHPWG_THEMES_PATH.'/'.$theme_id.'/admin/maintain.inc.php';
59
60    $errors = array();
61
62    switch ($action)
63    {
64      case 'activate':
65        if (isset($crt_db_theme))
66        {
67          // the theme is already active
68          break;
69        }
70
71        if ('default' == $theme_id)
72        {
73          // you can't activate the "default" theme
74          break;
75        }
76
77        $missing_parent = $this->missing_parent_theme($theme_id);
78        if (isset($missing_parent))
79        {
80          array_push(
81            $errors,
82            sprintf(
83              l10n('Impossible to activate this theme, the parent theme is missing: %s'),
84              $missing_parent
85              )
86            );
87
88          break;
89        }
90
91
92        if ($this->fs_themes[$theme_id]['mobile']
93            and !empty($conf['mobile_theme'])
94            and $conf['mobile_theme'] != $theme_id)
95        {
96          array_push($errors, l10n('You can activate only one mobile theme.'));
97          break;
98        }
99
100        if (file_exists($file_to_include))
101        {
102          include($file_to_include);
103          if (function_exists('theme_activate'))
104          {
105            theme_activate($theme_id, $this->fs_themes[$theme_id]['version'], $errors);
106          }
107        }
108
109        if (empty($errors))
110        {
111          $query = '
112INSERT INTO '.THEMES_TABLE.'
113  (id, version, name)
114  VALUES(\''.$theme_id.'\',
115         \''.$this->fs_themes[$theme_id]['version'].'\',
116         \''.$this->fs_themes[$theme_id]['name'].'\')
117;';
118          pwg_query($query);
119
120          if ($this->fs_themes[$theme_id]['mobile'])
121          {
122            conf_update_param('mobile_theme', $theme_id);
123          }
124        }
125        break;
126
127      case 'deactivate':
128        if (!isset($crt_db_theme))
129        {
130          // the theme is already inactive
131          break;
132        }
133
134        // you can't deactivate the last theme
135        if (count($this->db_themes_by_id) <= 1)
136        {
137          array_push(
138            $errors,
139            l10n('Impossible to deactivate this theme, you need at least one theme.')
140            );
141          break;
142        }
143
144        if ($theme_id == get_default_theme())
145        {
146          // find a random theme to replace
147          $new_theme = null;
148
149          $query = '
150SELECT
151    id
152  FROM '.THEMES_TABLE.'
153  WHERE id != \''.$theme_id.'\'
154;';
155          $result = pwg_query($query);
156          if (pwg_db_num_rows($result) == 0)
157          {
158            $new_theme = 'default';
159          }
160          else
161          {
162            list($new_theme) = pwg_db_fetch_row($result);
163          }
164
165          $this->set_default_theme($new_theme);
166        }
167
168        if (file_exists($file_to_include))
169        {
170          include($file_to_include);
171          if (function_exists('theme_deactivate'))
172          {
173            theme_deactivate($theme_id);
174          }
175        }
176
177        $query = '
178DELETE
179  FROM '.THEMES_TABLE.'
180  WHERE id= \''.$theme_id.'\'
181;';
182        pwg_query($query);
183
184        if ($this->fs_themes[$theme_id]['mobile'])
185        {
186          conf_update_param('mobile_theme', '');
187        }
188        break;
189
190      case 'delete':
191        if (!empty($crt_db_theme))
192        {
193          array_push($errors, 'CANNOT DELETE - THEME IS INSTALLED');
194          break;
195        }
196        if (!isset($this->fs_themes[$theme_id]))
197        {
198          // nothing to do here
199          break;
200        }
201
202        $children = $this->get_children_themes($theme_id);
203        if (count($children) > 0)
204        {
205          array_push(
206            $errors,
207            sprintf(
208              l10n('Impossible to delete this theme. Other themes depends on it: %s'),
209              implode(', ', $children)
210              )
211            );
212          break;
213        }
214
215        if (!$this->deltree(PHPWG_THEMES_PATH.$theme_id))
216        {
217          $this->send_to_trash(PHPWG_THEMES_PATH.$theme_id);
218        }
219        break;
220
221      case 'set_default':
222        // first we need to know which users are using the current default theme
223        $this->set_default_theme($theme_id);
224        break;
225    }
226    return $errors;
227  }
228
229  function missing_parent_theme($theme_id)
230  {
231    if (!isset($this->fs_themes[$theme_id]['parent']))
232    {
233      return null;
234    }
235
236    $parent = $this->fs_themes[$theme_id]['parent'];
237
238    if ('default' == $parent)
239    {
240      return null;
241    }
242
243    if (!isset($this->fs_themes[$parent]))
244    {
245      return $parent;
246    }
247
248    return $this->missing_parent_theme($parent);
249  }
250
251  function get_children_themes($theme_id)
252  {
253    $children = array();
254
255    foreach ($this->fs_themes as $test_child)
256    {
257      if (isset($test_child['parent']) and $test_child['parent'] == $theme_id)
258      {
259        array_push($children, $test_child['name']);
260      }
261    }
262
263    return $children;
264  }
265
266  function set_default_theme($theme_id)
267  {
268    global $conf;
269
270    // first we need to know which users are using the current default theme
271    $default_theme = get_default_theme();
272
273    $query = '
274SELECT
275    user_id
276  FROM '.USER_INFOS_TABLE.'
277  WHERE theme = \''.$default_theme.'\'
278;';
279    $user_ids = array_unique(
280      array_merge(
281        array_from_query($query, 'user_id'),
282        array($conf['guest_id'], $conf['default_user_id'])
283        )
284      );
285
286    // $user_ids can't be empty, at least the default user has the default
287    // theme
288
289    $query = '
290UPDATE '.USER_INFOS_TABLE.'
291  SET theme = \''.$theme_id.'\'
292  WHERE user_id IN ('.implode(',', $user_ids).')
293;';
294    pwg_query($query);
295  }
296
297  function get_db_themes($id='')
298  {
299    $query = '
300SELECT
301    *
302  FROM '.THEMES_TABLE;
303
304    $clauses = array();
305    if (!empty($id))
306    {
307      $clauses[] = 'id = \''.$id.'\'';
308    }
309    if (count($clauses) > 0)
310    {
311      $query .= '
312  WHERE '. implode(' AND ', $clauses);
313    }
314
315    $result = pwg_query($query);
316    $themes = array();
317    while ($row = pwg_db_fetch_assoc($result))
318    {
319      array_push($themes, $row);
320    }
321    return $themes;
322  }
323
324
325  /**
326  *  Get themes defined in the theme directory
327  */
328  function get_fs_themes()
329  {
330    $dir = opendir(PHPWG_THEMES_PATH);
331
332    while ($file = readdir($dir))
333    {
334      if ($file!='.' and $file!='..')
335      {
336        $path = PHPWG_THEMES_PATH.$file;
337        if (is_dir($path)
338            and preg_match('/^[a-zA-Z0-9-_]+$/', $file)
339            and file_exists($path.'/themeconf.inc.php')
340            )
341        {
342          $theme = array(
343            'id' => $file,
344            'name' => $file,
345            'version' => '0',
346            'uri' => '',
347            'description' => '',
348            'author' => '',
349            'mobile' => false,
350            );
351          $theme_data = implode( '', file($path.'/themeconf.inc.php') );
352
353          if ( preg_match("|Theme Name: (.*)|", $theme_data, $val) )
354          {
355            $theme['name'] = trim( $val[1] );
356          }
357          if (preg_match("|Version: (.*)|", $theme_data, $val))
358          {
359            $theme['version'] = trim($val[1]);
360          }
361          if ( preg_match("|Theme URI: (.*)|", $theme_data, $val) )
362          {
363            $theme['uri'] = trim($val[1]);
364          }
365          if ($desc = load_language('description.txt', $path.'/', array('return' => true)))
366          {
367            $theme['description'] = trim($desc);
368          }
369          elseif ( preg_match("|Description: (.*)|", $theme_data, $val) )
370          {
371            $theme['description'] = trim($val[1]);
372          }
373          if ( preg_match("|Author: (.*)|", $theme_data, $val) )
374          {
375            $theme['author'] = trim($val[1]);
376          }
377          if ( preg_match("|Author URI: (.*)|", $theme_data, $val) )
378          {
379            $theme['author uri'] = trim($val[1]);
380          }
381          if (!empty($theme['uri']) and strpos($theme['uri'] , 'extension_view.php?eid='))
382          {
383            list( , $extension) = explode('extension_view.php?eid=', $theme['uri']);
384            if (is_numeric($extension)) $theme['extension'] = $extension;
385          }
386          if (preg_match('/["\']parent["\'][^"\']+["\']([^"\']+)["\']/', $theme_data, $val))
387          {
388            $theme['parent'] = $val[1];
389          }
390          if (preg_match('/["\']activable["\'].*?(true|false)/i', $theme_data, $val))
391          {
392            $theme['activable'] = get_boolean($val[1]);
393          }
394          if (preg_match('/["\']mobile["\'].*?(true|false)/i', $theme_data, $val))
395          {
396            $theme['mobile'] = get_boolean($val[1]);
397          }
398
399          // screenshot
400          $screenshot_path = $path.'/screenshot.png';
401          if (file_exists($screenshot_path))
402          {
403            $theme['screenshot'] = $screenshot_path;
404          }
405          else
406          {
407            global $conf;
408            $theme['screenshot'] =
409              PHPWG_ROOT_PATH.'admin/themes/'
410              .$conf['admin_theme']
411              .'/images/missing_screenshot.png'
412              ;
413          }
414
415          $admin_file = $path.'/admin/admin.inc.php';
416          if (file_exists($admin_file))
417          {
418            $theme['admin_uri'] = get_root_url().'admin.php?page=theme&theme='.$file;
419          }
420
421          // IMPORTANT SECURITY !
422          $theme = array_map('htmlspecialchars', $theme);
423          $this->fs_themes[$file] = $theme;
424        }
425      }
426    }
427    closedir($dir);
428  }
429
430  /**
431   * Sort fs_themes
432   */
433  function sort_fs_themes($order='name')
434  {
435    switch ($order)
436    {
437      case 'name':
438        uasort($this->fs_themes, 'name_compare');
439        break;
440      case 'status':
441        $this->sort_themes_by_state();
442        break;
443      case 'author':
444        uasort($this->fs_themes, array($this, 'theme_author_compare'));
445        break;
446      case 'id':
447        uksort($this->fs_themes, 'strcasecmp');
448        break;
449    }
450  }
451
452  /**
453   * Retrieve PEM server datas to $server_themes
454   */
455  function get_server_themes($new=false)
456  {
457    global $user;
458
459    $get_data = array(
460      'category_id' => 10,
461      'format' => 'php',
462    );
463
464    // Retrieve PEM versions
465    $version = PHPWG_VERSION;
466    $versions_to_check = array();
467    $url = PEM_URL . '/api/get_version_list.php';
468    if (fetchRemote($url, $result, $get_data) and $pem_versions = @unserialize($result))
469    {
470      if (!preg_match('/^\d+\.\d+\.\d+$/', $version))
471      {
472        $version = $pem_versions[0]['name'];
473      }
474      $branch = get_branch_from_version($version);
475      foreach ($pem_versions as $pem_version)
476      {
477        if (strpos($pem_version['name'], $branch) === 0)
478        {
479          $versions_to_check[] = $pem_version['id'];
480        }
481      }
482    }
483    if (empty($versions_to_check))
484    {
485      return false;
486    }
487
488    // Themes to check
489    $themes_to_check = array();
490    foreach($this->fs_themes as $fs_theme)
491    {
492      if (isset($fs_theme['extension']))
493      {
494        $themes_to_check[] = $fs_theme['extension'];
495      }
496    }
497
498    // Retrieve PEM themes infos
499    $url = PEM_URL . '/api/get_revision_list.php';
500    $get_data = array_merge($get_data, array(
501      'last_revision_only' => 'true',
502      'version' => implode(',', $versions_to_check),
503      'lang' => substr($user['language'], 0, 2),
504      'get_nb_downloads' => 'true',
505      )
506    );
507
508    if (!empty($themes_to_check))
509    {
510      if ($new)
511      {
512        $get_data['extension_exclude'] = implode(',', $themes_to_check);
513      }
514      else
515      {
516        $get_data['extension_include'] = implode(',', $themes_to_check);
517      }
518    }
519    if (fetchRemote($url, $result, $get_data))
520    {
521      $pem_themes = @unserialize($result);
522      if (!is_array($pem_themes))
523      {
524        return false;
525      }
526      foreach ($pem_themes as $theme)
527      {
528        $this->server_themes[$theme['extension_id']] = $theme;
529      }
530      return true;
531    }
532    return false;
533  }
534
535  /**
536   * Sort $server_themes
537   */
538  function sort_server_themes($order='date')
539  {
540    switch ($order)
541    {
542      case 'date':
543        krsort($this->server_themes);
544        break;
545      case 'revision':
546        usort($this->server_themes, array($this, 'extension_revision_compare'));
547        break;
548      case 'name':
549        uasort($this->server_themes, array($this, 'extension_name_compare'));
550        break;
551      case 'author':
552        uasort($this->server_themes, array($this, 'extension_author_compare'));
553        break;
554      case 'downloads':
555        usort($this->server_themes, array($this, 'extension_downloads_compare'));
556        break;
557    }
558  }
559
560  /**
561   * Extract theme files from archive
562   *
563   * @param string - install or upgrade
564   * @param string - remote revision identifier (numeric)
565   * @param string - theme id or extension id
566   */
567  function extract_theme_files($action, $revision, $dest)
568  {
569    if ($archive = tempnam( PHPWG_THEMES_PATH, 'zip'))
570    {
571      $url = PEM_URL . '/download.php';
572      $get_data = array(
573        'rid' => $revision,
574        'origin' => 'piwigo_'.$action,
575      );
576
577      if ($handle = @fopen($archive, 'wb') and fetchRemote($url, $handle, $get_data))
578      {
579        fclose($handle);
580        include_once(PHPWG_ROOT_PATH.'admin/include/pclzip.lib.php');
581        $zip = new PclZip($archive);
582        if ($list = $zip->listContent())
583        {
584          foreach ($list as $file)
585          {
586            // we search main.inc.php in archive
587            if (basename($file['filename']) == 'themeconf.inc.php'
588              and (!isset($main_filepath)
589              or strlen($file['filename']) < strlen($main_filepath)))
590            {
591              $main_filepath = $file['filename'];
592            }
593          }
594          if (isset($main_filepath))
595          {
596            $root = dirname($main_filepath); // main.inc.php path in archive
597            if ($action == 'upgrade')
598            {
599              $extract_path = PHPWG_THEMES_PATH . $dest;
600            }
601            else
602            {
603              $extract_path = PHPWG_THEMES_PATH . ($root == '.' ? 'extension_' . $dest : basename($root));
604            }
605            if (
606              $result = $zip->extract(
607                PCLZIP_OPT_PATH, $extract_path,
608                PCLZIP_OPT_REMOVE_PATH, $root,
609                PCLZIP_OPT_REPLACE_NEWER
610                )
611              )
612            {
613              foreach ($result as $file)
614              {
615                if ($file['stored_filename'] == $main_filepath)
616                {
617                  $status = $file['status'];
618                  break;
619                }
620              }
621              if (file_exists($extract_path.'/obsolete.list')
622                and $old_files = file($extract_path.'/obsolete.list', FILE_IGNORE_NEW_LINES)
623                and !empty($old_files))
624              {
625                array_push($old_files, 'obsolete.list');
626                foreach($old_files as $old_file)
627                {
628                  $path = $extract_path.'/'.$old_file;
629                  if (is_file($path))
630                  {
631                    @unlink($path);
632                  }
633                  elseif (is_dir($path))
634                  {
635                    if (!$this->deltree($path))
636                    {
637                      $this->send_to_trash($path);
638                    }
639                  }
640                }
641              }
642            }
643            else $status = 'extract_error';
644          }
645          else $status = 'archive_error';
646        }
647        else $status = 'archive_error';
648      }
649      else $status = 'dl_archive_error';
650    }
651    else $status = 'temp_path_error';
652
653    @unlink($archive);
654    return $status;
655  }
656
657  /**
658   * delete $path directory
659   * @param string - path
660   */
661  function deltree($path)
662  {
663    if (is_dir($path))
664    {
665      $fh = opendir($path);
666      while ($file = readdir($fh))
667      {
668        if ($file != '.' and $file != '..')
669        {
670          $pathfile = $path . '/' . $file;
671          if (is_dir($pathfile))
672          {
673            $this->deltree($pathfile);
674          }
675          else
676          {
677            @unlink($pathfile);
678          }
679        }
680      }
681      closedir($fh);
682      return @rmdir($path);
683    }
684  }
685
686  /**
687   * send $path to trash directory
688   * @param string - path
689   */
690  function send_to_trash($path)
691  {
692    $trash_path = PHPWG_THEMES_PATH . 'trash';
693    if (!is_dir($trash_path))
694    {
695      @mkdir($trash_path);
696      $file = @fopen($trash_path . '/.htaccess', 'w');
697      @fwrite($file, 'deny from all');
698      @fclose($file);
699    }
700    while ($r = $trash_path . '/' . md5(uniqid(rand(), true)))
701    {
702      if (!is_dir($r))
703      {
704        @rename($path, $r);
705        break;
706      }
707    }
708  }
709
710  /**
711   * Sort functions
712   */
713  function theme_version_compare($a, $b)
714  {
715    $pattern = array('/([a-z])/ei', '/\.+/', '/\.\Z|\A\./');
716    $replacement = array( "'.'.intval('\\1', 36).'.'", '.', '');
717
718    $array = preg_replace($pattern, $replacement, array($a, $b));
719
720    return version_compare($array[0], $array[1], '>=');
721  }
722
723  function extension_revision_compare($a, $b)
724  {
725    if ($a['revision_date'] < $b['revision_date']) return 1;
726    else return -1;
727  }
728
729  function extension_name_compare($a, $b)
730  {
731    return strcmp(strtolower($a['extension_name']), strtolower($b['extension_name']));
732  }
733
734  function extension_author_compare($a, $b)
735  {
736    $r = strcasecmp($a['author_name'], $b['author_name']);
737    if ($r == 0) return $this->extension_name_compare($a, $b);
738    else return $r;
739  }
740
741  function theme_author_compare($a, $b)
742  {
743    $r = strcasecmp($a['author'], $b['author']);
744    if ($r == 0) return name_compare($a, $b);
745    else return $r;
746  }
747
748  function extension_downloads_compare($a, $b)
749  {
750    if ($a['extension_nb_downloads'] < $b['extension_nb_downloads']) return 1;
751    else return -1;
752  }
753
754  function sort_themes_by_state()
755  {
756    uasort($this->fs_themes, 'name_compare');
757
758    $active_themes = array();
759    $inactive_themes = array();
760    $not_installed = array();
761
762    foreach($this->fs_themes as $theme_id => $theme)
763    {
764      if (isset($this->db_themes_by_id[$theme_id]))
765      {
766        $this->db_themes_by_id[$theme_id]['state'] == 'active' ?
767          $active_themes[$theme_id] = $theme : $inactive_themes[$theme_id] = $theme;
768      }
769      else
770      {
771        $not_installed[$theme_id] = $theme;
772      }
773    }
774    $this->fs_themes = $active_themes + $inactive_themes + $not_installed;
775  }
776
777}
778?>
Note: See TracBrowser for help on using the repository browser.