Ticket #2638: allow themes to specify a base theme to load with 'include' setting in a theme.ini file

This commit is contained in:
Brion Vibber 2010-09-02 14:11:52 -07:00
parent 11f7fce3bb
commit c24458a9f0
4 changed files with 82 additions and 4 deletions

View File

@ -200,7 +200,7 @@ class Action extends HTMLOutputter // lawsuit
if (Event::handle('StartShowStatusNetStyles', array($this)) && if (Event::handle('StartShowStatusNetStyles', array($this)) &&
Event::handle('StartShowLaconicaStyles', array($this))) { Event::handle('StartShowLaconicaStyles', array($this))) {
$this->cssLink('css/display.css',null, 'screen, projection, tv, print'); $this->primaryCssLink(null, 'screen, projection, tv, print');
Event::handle('EndShowStatusNetStyles', array($this)); Event::handle('EndShowStatusNetStyles', array($this));
Event::handle('EndShowLaconicaStyles', array($this)); Event::handle('EndShowLaconicaStyles', array($this));
} }
@ -248,6 +248,18 @@ class Action extends HTMLOutputter // lawsuit
} }
} }
function primaryCssLink($mainTheme=null, $media=null)
{
// If the currently-selected theme has dependencies on other themes,
// we'll need to load their display.css files as well in order.
$theme = new Theme($mainTheme);
$baseThemes = $theme->getDeps();
foreach ($baseThemes as $baseTheme) {
$this->cssLink('css/display.css', $baseTheme, $media);
}
$this->cssLink('css/display.css', $mainTheme, $media);
}
/** /**
* Show javascript headers * Show javascript headers
* *

View File

@ -54,6 +54,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
class Theme class Theme
{ {
var $name = null;
var $dir = null; var $dir = null;
var $path = null; var $path = null;
@ -70,6 +71,10 @@ class Theme
if (empty($name)) { if (empty($name)) {
$name = common_config('site', 'theme'); $name = common_config('site', 'theme');
} }
if (!self::validName($name)) {
throw new ServerException("Invalid theme name.");
}
$this->name = $name;
// Check to see if it's in the local dir // Check to see if it's in the local dir
@ -177,6 +182,58 @@ class Theme
return $this->path.'/'.$relative; return $this->path.'/'.$relative;
} }
/**
* Fetch a list of other themes whose CSS needs to be pulled in before
* this theme's, based on following the theme.ini 'include' settings.
* (May be empty if this theme has no include dependencies.)
*
* @return array of strings with theme names
*/
function getDeps()
{
$chain = $this->doGetDeps(array($this->name));
array_pop($chain); // Drop us back off
return $chain;
}
protected function doGetDeps($chain)
{
$data = $this->getMetadata();
if (!empty($data['include'])) {
$include = $data['include'];
// Protect against cycles!
if (!in_array($include, $chain)) {
try {
$theme = new Theme($include);
array_unshift($chain, $include);
return $theme->doGetDeps($chain);
} catch (Exception $e) {
common_log(LOG_ERR,
"Exception while fetching theme dependencies " .
"for $this->name: " . $e->getMessage());
}
}
}
return $chain;
}
/**
* Pull data from the theme's theme.ini file.
* @fixme calling getFile will fall back to default theme, this may be unsafe.
*
* @return associative array of strings
*/
function getMetadata()
{
$iniFile = $this->getFile('theme.ini');
if (file_exists($iniFile)) {
return parse_ini_file($iniFile);
} else {
return array();
}
}
/** /**
* Gets the full path of a file in a theme dir based on its relative name * Gets the full path of a file in a theme dir based on its relative name
* *
@ -285,4 +342,9 @@ class Theme
return $instroot; return $instroot;
} }
static function validName($name)
{
return preg_match('/^[a-z0-9][a-z0-9_-]*$/i', $name);
}
} }

View File

@ -198,7 +198,7 @@ class ThemeUploader
protected function validateFile($filename, $ext) protected function validateFile($filename, $ext)
{ {
$this->validateFileOrFolder($filename); $this->validateFileOrFolder($filename);
$this->validateExtension($ext); $this->validateExtension($filename, $ext);
// @fixme validate content // @fixme validate content
} }
@ -216,13 +216,17 @@ class ThemeUploader
return true; return true;
} }
protected function validateExtension($ext) protected function validateExtension($base, $ext)
{ {
$allowed = array('css', // CSS may need validation $allowed = array('css', // CSS may need validation
'png', 'gif', 'jpg', 'jpeg', 'png', 'gif', 'jpg', 'jpeg',
'svg', // SVG images/fonts may need validation 'svg', // SVG images/fonts may need validation
'ttf', 'eot', 'woff'); 'ttf', 'eot', 'woff');
if (!in_array(strtolower($ext), $allowed)) { if (!in_array(strtolower($ext), $allowed)) {
if ($ext == 'ini' && $base == 'theme') {
// theme.ini exception
return true;
}
$msg = sprintf(_("Theme contains file of type '.%s', " . $msg = sprintf(_("Theme contains file of type '.%s', " .
"which is not allowed."), "which is not allowed."),
$ext); $ext);

View File

@ -241,7 +241,7 @@ class MobileProfilePlugin extends WAP20Plugin
return true; return true;
} }
$action->cssLink('css/display.css'); $action->primaryCssLink();
if (file_exists(Theme::file('css/mp-screen.css'))) { if (file_exists(Theme::file('css/mp-screen.css'))) {
$action->cssLink('css/mp-screen.css', null, 'screen'); $action->cssLink('css/mp-screen.css', null, 'screen');