plugins/admin/ThemeManager.php
author Dan
Sun, 04 May 2008 21:57:48 -0400
changeset 541 acb7e23b6ffa
parent 536 218a627eb53e
child 685 17ebe24cdf85
permissions -rw-r--r--
Massive commit with various changes. Added user ranks system (no admin interface yet) and ability for users to have custom user titles. Made cron framework accept fractions of hours through floating-point intervals. Modifed ACL editor to use miniPrompt framework for close confirmation box. Made avatar system use a special page as opposed to fetching the files directly for caching reasons.

<?php

/*
 * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
 * Version 1.1.4 (Caoineag alpha 4)
 * Copyright (C) 2006-2008 Dan Fuhry
 *
 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
 */

function page_Admin_ThemeManager($force_no_json = false)
{
  global $db, $session, $paths, $template, $plugins; // Common objects
  global $lang;
  if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
  {
    $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
    echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
    echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
    return;
  }
  
  $system_themes =& $template->system_themes;
  
  // Obtain the list of themes (both available and already installed) and the styles available for each
  $dh = @opendir(ENANO_ROOT . '/themes');
  if ( !$dh )
    die('Couldn\'t open themes directory');
  $themes = array();
  while ( $dr = @readdir($dh) )
  {
    if ( $dr == '.' || $dr == '..' )
      continue;
    if ( !is_dir(ENANO_ROOT . "/themes/$dr") )
      continue;
    if ( !file_exists(ENANO_ROOT . "/themes/$dr/theme.cfg") || !is_dir(ENANO_ROOT . "/themes/$dr/css") )
      continue;
    $cdh = @opendir(ENANO_ROOT . "/themes/$dr/css");
    if ( !$cdh )
      continue;
    
    require(ENANO_ROOT . "/themes/$dr/theme.cfg");
    global $theme;
    
    $themes[$dr] = array(
        'css' => array(),
        'theme_name' => $theme['theme_name']
      );
    while ( $cdr = @readdir($cdh) )
    {
      if ( $cdr == '.' || $cdr == '..' )
        continue;
      if ( preg_match('/\.css$/i', $cdr) )
        $themes[$dr]['css'][] = substr($cdr, 0, -4);
    }
  }
  
  // Decide which themes are not installed
  $installable = array_flip(array_keys($themes));
  // FIXME: sanitize directory names or check with preg_match()
  $where_clause = 'theme_id = \'' . implode('\' OR theme_id = \'', array_flip($installable)) . '\'';
  $q = $db->sql_query('SELECT theme_id, theme_name, enabled FROM ' . table_prefix . "themes WHERE $where_clause;");
  if ( !$q )
    $db->_die();
  
  while ( $row = $db->fetchrow() )
  {
    $tid =& $row['theme_id'];
    unset($installable[$tid]);
    $themes[$tid]['theme_name'] = $row['theme_name'];
    $themes[$tid]['enabled'] = ( $row['enabled'] == 1 );
  }
  
  foreach ( $system_themes as $st )
  {
    unset($installable[$st]);
  }
  
  $installable = array_flip($installable);
  
  // AJAX code
  if ( $paths->getParam(0) === 'action.json' && !$force_no_json )
  {
    return ajaxServlet_Admin_ThemeManager($themes);
  }
  
  // List installed themes
  ?>
  <div style="float: right;">
    <a href="#" id="systheme_toggler" onclick="ajaxToggleSystemThemes(); return false;"><?php echo $lang->get('acptm_btn_system_themes_show'); ?></a>
  </div>
  <?php
  echo '<h3>' . $lang->get('acptm_heading_edit_themes') . '</h3>';
  echo '<div id="theme_list_edit">';
  foreach ( $themes as $theme_id => $theme_data )
  {
    if ( in_array($theme_id, $installable) )
      continue;
    if ( file_exists(ENANO_ROOT . "/themes/$theme_id/preview.png") )
    {
      $preview_path = scriptPath . "/themes/$theme_id/preview.png";
    }
    else
    {
      $preview_path = scriptPath . "/images/themepreview.png";
    }
    $d = ( @$theme_data['enabled'] ) ? '' : ' themebutton_theme_disabled';
    $st = ( in_array($theme_id, $system_themes) ) ? ' themebutton_theme_system' : '';
    echo '<div class="themebutton' . $st . '' . $d . '" id="themebtn_edit_' . $theme_id . '" style="background-image: url(' . $preview_path . ');">';
    if ( in_array($theme_id, $system_themes) )
    {
      echo   '<a class="tb-inner" href="#" onclick="return false;">
                ' . $lang->get('acptm_btn_theme_system') . '
                <span class="themename">' . htmlspecialchars($theme_data['theme_name']) . '</span>
              </a>';
    }
    else
    {
      echo   '<a class="tb-inner" href="#" onclick="ajaxEditTheme(\'' . $theme_id . '\'); return false;">
                ' . $lang->get('acptm_btn_theme_edit') . '
                <span class="themename">' . htmlspecialchars($theme_data['theme_name']) . '</span>
              </a>';
    }
    echo '</div>';
  }
  echo '</div>';
  echo '<span class="menuclear"></span>';
  
  if ( count($installable) > 0 )
  {
    echo '<h3>' . $lang->get('acptm_heading_install_themes') . '</h3>';
  
    echo '<div id="theme_list_install">';
    foreach ( $installable as $i => $theme_id )
    {
      if ( file_exists(ENANO_ROOT . "/themes/$theme_id/preview.png") )
      {
        $preview_path = scriptPath . "/themes/$theme_id/preview.png";
      }
      else
      {
        $preview_path = scriptPath . "/images/themepreview.png";
      }
      echo '<div class="themebutton" id="themebtn_install_' . $theme_id . '" enano:themename="' . htmlspecialchars($themes[$theme_id]['theme_name']) . '" style="background-image: url(' . $preview_path . ');">';
      echo   '<a class="tb-inner" href="#" onclick="ajaxInstallTheme(\'' . $theme_id . '\'); return false;">
                ' . $lang->get('acptm_btn_theme_install') . '
                <span class="themename">' . htmlspecialchars($themes[$theme_id]['theme_name']) . '</span>
              </a>';
      echo '</div>';
    }
    echo '</div>';
    echo '<span class="menuclear"></span>';
  }
}

function ajaxServlet_Admin_ThemeManager(&$themes)
{
  global $db, $session, $paths, $template, $plugins; // Common objects
  global $lang;
  if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
  {
    $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
    echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
    echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
    return;
  }
  
  if ( !isset($_POST['r']) )
    return false;
  
  try
  {
    $request = enano_json_decode($_POST['r']);
  }
  catch ( Exception $e )
  {
    die('Exception in JSON parser, probably invalid input.');
  }
  
  if ( !isset($request['mode']) )
  {
    die('No mode specified in JSON request.');
  }
  
  switch ( $request['mode'] )
  {
    case 'fetch_theme':
      $theme_id = $db->escape($request['theme_id']);
      if ( empty($theme_id) )
        die('Invalid theme_id');
      
      $q = $db->sql_query("SELECT theme_id, theme_name, default_style, enabled, group_policy, group_list FROM " . table_prefix . "themes WHERE theme_id = '$theme_id';");
      if ( !$q )
        $db->die_json();
      
      if ( $db->numrows() < 1 )
        die('BUG: no theme with that theme_id installed.');
      
      $row = $db->fetchrow();
      $row['enabled'] = ( $row['enabled'] == 1 );
      $row['css'] = @$themes[$theme_id]['css'];
      $row['default_style'] = preg_replace('/\.css$/', '', $row['default_style']);
      $row['is_default'] = ( getConfig('theme_default') === $theme_id );
      $row['group_list'] = ( empty($row['group_list']) ) ? array() : enano_json_decode($row['group_list']);
      
      // Build a list of group names
      $row['group_names'] = array();
      $q = $db->sql_query('SELECT group_id, group_name FROM ' . table_prefix . 'groups;');
      if ( !$q )
        $db->die_json();
      while ( $gr = $db->fetchrow() )
      {
        $row['group_names'][ intval($gr['group_id']) ] = $gr['group_name'];
      }
      $db->free_result();
      
      // Build a list of usernames
      $row['usernames'] = array();
      foreach ( $row['group_list'] as $el )
      {
        if ( !preg_match('/^u:([0-9]+)$/', $el, $match) )
          continue;
        $uid =& $match[1];
        $q = $db->sql_query('SELECT username FROM ' . table_prefix . "users WHERE user_id = $uid;");
        if ( !$q )
          $db->die_json();
        if ( $db->numrows() < 1 )
        {
          $db->free_result();
          continue;
        }
        list($username) = $db->fetchrow_num();
        $row['usernames'][$uid] = $username;
        $db->free_result();
      }
      
      echo enano_json_encode($row);
      break;
    case 'uid_lookup':
      $username = @$request['username'];
      if ( empty($username) )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => $lang->get('acptm_err_invalid_username')
          )));
      }
      $username = $db->escape(strtolower($username));
      $q = $db->sql_query('SELECT user_id, username FROM ' . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '$username';");
      if ( !$q )
        $db->die_json();
      
      if ( $db->numrows() < 1 )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => $lang->get('acptm_err_username_not_found')
          )));
      }
      
      list($uid, $username_real) = $db->fetchrow_num();
      $db->free_result();
      
      echo enano_json_encode(array(
          'uid' => $uid,
          'username' => $username_real
        ));
      break;
    case 'save_theme':
      if ( !isset($request['theme_data']) )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => 'No theme data in request'
          )));
      }
      $theme_data =& $request['theme_data'];
      // Perform integrity check on theme data
      $chk_theme_exists = isset($themes[@$theme_data['theme_id']]);
      $theme_data['theme_name'] = trim(@$theme_data['theme_name']);
      $chk_name_good = !empty($theme_data['theme_name']);
      $chk_policy_good = in_array(@$theme_data['group_policy'], array('allow_all', 'whitelist', 'blacklist'));
      $chk_grouplist_good = true;
      foreach ( $theme_data['group_list'] as $acl_entry )
      {
        if ( !preg_match('/^(u|g):[0-9]+$/', $acl_entry) )
        {
          $chk_grouplist_good = false;
          break;
        }
      }
      $chk_style_good = @in_array(@$theme_data['default_style'], @$themes[@$theme_data['theme_id']]['css']);
      if ( !$chk_theme_exists || !$chk_name_good || !$chk_policy_good || !$chk_grouplist_good || !$chk_style_good )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => $lang->get('acptm_err_save_validation_failed')
          )));
      }
      
      $enable = ( $theme_data['enabled'] ) ? '1' : '0';
      $theme_default = getConfig('theme_default');
      $warn_default = ( $theme_default === $theme_data['theme_id'] || $theme_data['make_default'] ) ?
                        ' ' . $lang->get('acptm_warn_access_with_default') . ' ' :
                        ' ';
      if ( $enable == 0 && ( $theme_default === $theme_data['theme_id'] || $theme_data['make_default'] ) )
      {
        $enable = '1';
        $warn_default .= '<b>' . $lang->get('acptm_warn_cant_disable_default') . '</b>';
      }
      
      // We're good. Update the theme...
      $q = $db->sql_query('UPDATE ' . table_prefix . 'themes SET
                               theme_name = \'' . $db->escape($theme_data['theme_name']) . '\',
                               default_style = \'' . $db->escape($theme_data['default_style']) . '\',
                               group_list = \'' . $db->escape(enano_json_encode($theme_data['group_list'])) . '\',
                               group_policy = \'' . $db->escape($theme_data['group_policy']) . '\',
                               enabled = ' . $enable . '
                             WHERE theme_id = \'' . $db->escape($theme_data['theme_id']) . '\';');
      if ( !$q )
        $db->die_json();
      
      if ( $theme_data['make_default'] )
      {
        setConfig('theme_default', $theme_data['theme_id']);
      }
      
      echo '<div class="info-box"><b>' . $lang->get('acptm_msg_save_success') . '</b>' . $warn_default . '</div>';
      
      page_Admin_ThemeManager(true);
      break;
    case 'install':
      $theme_id =& $request['theme_id'];
      if ( !isset($themes[$theme_id]) )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => 'Theme was deleted from themes/ directory or couldn\'t read theme metadata from filesystem'
          )));
      }
      if ( !isset($themes[$theme_id]['css'][0]) )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => 'Theme doesn\'t have any files in css/, thus it can\'t be installed. (translators: l10n?)'
          )));
      }
      // build dataset
      $theme_name = $db->escape($themes[$theme_id]['theme_name']);
      $default_style = $db->escape($themes[$theme_id]['css'][0]);
      $theme_id = $db->escape($theme_id);
      
      // insert it
      $q = $db->sql_query('INSERT INTO ' . table_prefix . "themes(theme_id, theme_name, default_style, enabled, group_list, group_policy)\n"
                        . "  VALUES( '$theme_id', '$theme_name', '$default_style', 1, '[]', 'allow_all' );");
      if ( !$q )
        $db->die_json();
      
      // The response isn't processed unless it's in JSON.
      echo 'Roger that, over and out.';
      
      break;
    case 'uninstall':
      $theme_id =& $request['theme_id'];
      $theme_default = getConfig('theme_default');
      
      // Validation
      if ( !isset($themes[$theme_id]) )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => 'Theme was deleted from themes/ directory or couldn\'t read theme metadata from filesystem'
          )));
      }
      
      if ( $theme_id == $theme_default )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => $lang->get('acptm_err_uninstalling_default')
          )));
      }
      
      if ( $theme_id == 'oxygen' )
      {
        die(enano_json_encode(array(
            'mode' => 'error',
            'error' => $lang->get('acptm_err_uninstalling_oxygen')
          )));
      }
      
      $theme_id = $db->escape($theme_id);
      
      $q = $db->sql_query('DELETE FROM ' . table_prefix . "themes WHERE theme_id = '$theme_id';");
      if ( !$q )
        $db->die_json();
      
      // Change all the users that were on that theme to the default
      $default_style = $template->named_theme_list[$theme_default]['default_style'];
      $default_style = preg_replace('/\.css$/', '', $default_style);
      
      $theme_default = $db->escape($theme_default);
      $default_style = $db->escape($default_style);
      
      $q = $db->sql_query('UPDATE ' . table_prefix . "users SET theme = '$theme_default', style = '$default_style' WHERE theme = '$theme_id';");
      if ( !$q )
        $db->die_json();
      
      echo '<div class="info-box">' . $lang->get('acptm_msg_uninstall_success') . '</div>';
      
      page_Admin_ThemeManager(true);
      break;
  }
}