source: branches/2.1/admin/include/themes.class.php @ 9552

Last change on this file since 9552 was 8240, checked in by plg, 13 years ago

merge r8238 from trunk to branch 2.1

feature 2048 removed (and was never released): no data are sent anonymously to piwigo.org for statistics purpose

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