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

Last change on this file since 29068 was 28969, checked in by mistic100, 10 years ago

improves regexes parsing plugins metadata

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