source: trunk/admin/include/themes.class.php @ 5259

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

ture 1514: improvement, impossible to activate a theme is a parent is missing
(a parent includes grand father and his own father, and his own father, and so
on... until the root theme is "default" or has no parent declared)

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