We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit 47665e84 authored by Brion Vibber's avatar Brion Vibber

Merge branch 'testing' of gitorious.org:statusnet/mainline into testing

parents ec155464 054ac909
......@@ -125,9 +125,19 @@ class DesignadminpanelAction extends AdminPanelAction
return;
}
// check for an image upload
// check for file uploads
$bgimage = $this->saveBackgroundImage();
$customTheme = $this->saveCustomTheme();
$oldtheme = common_config('site', 'theme');
if ($customTheme) {
// This feels pretty hacky :D
$this->args['theme'] = $customTheme;
$themeChanged = true;
} else {
$themeChanged = ($this->trimmed('theme') != $oldtheme);
}
static $settings = array('theme', 'logo');
......@@ -139,15 +149,13 @@ class DesignadminpanelAction extends AdminPanelAction
$this->validate($values);
$oldtheme = common_config('site', 'theme');
$config = new Config();
$config->query('BEGIN');
// Only update colors if the theme has not changed.
if ($oldtheme == $values['theme']) {
if (!$themeChanged) {
$bgcolor = new WebColor($this->trimmed('design_background'));
$ccolor = new WebColor($this->trimmed('design_content'));
......@@ -189,6 +197,13 @@ class DesignadminpanelAction extends AdminPanelAction
Config::save('design', 'backgroundimage', $bgimage);
}
if (common_config('custom_css', 'enabled')) {
$css = $this->arg('css');
if ($css != common_config('custom_css', 'css')) {
Config::save('custom_css', 'css', $css);
}
}
$config->query('COMMIT');
}
......@@ -262,6 +277,33 @@ class DesignadminpanelAction extends AdminPanelAction
}
}
/**
* Save the custom theme if the user uploaded one.
*
* @return mixed custom theme name, if succesful, or null if no theme upload.
* @throws ClientException for invalid theme archives
* @throws ServerException if trouble saving the theme files
*/
function saveCustomTheme()
{
if (common_config('theme_upload', 'enabled') &&
$_FILES['design_upload_theme']['error'] == UPLOAD_ERR_OK) {
$upload = ThemeUploader::fromUpload('design_upload_theme');
$basedir = common_config('local', 'dir');
if (empty($basedir)) {
$basedir = INSTALLDIR . '/local';
}
$name = 'custom'; // @todo allow multiples, custom naming?
$outdir = $basedir . '/theme/' . $name;
$upload->extract($outdir);
return $name;
} else {
return null;
}
}
/**
* Attempt to validate setting values
*
......@@ -370,7 +412,15 @@ class DesignAdminPanelForm extends AdminForm
function formData()
{
$this->showLogo();
$this->showTheme();
$this->showBackground();
$this->showColors();
$this->showAdvanced();
}
function showLogo()
{
$this->out->elementStart('fieldset', array('id' => 'settings_design_logo'));
$this->out->element('legend', null, _('Change logo'));
......@@ -383,6 +433,11 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
function showTheme()
{
$this->out->elementStart('fieldset', array('id' => 'settings_design_theme'));
$this->out->element('legend', null, _('Change theme'));
......@@ -406,10 +461,23 @@ class DesignAdminPanelForm extends AdminForm
false, $this->value('theme'));
$this->unli();
if (common_config('theme_upload', 'enabled')) {
$this->li();
$this->out->element('label', array('for' => 'design_upload_theme'), _('Custom theme'));
$this->out->element('input', array('id' => 'design_upload_theme',
'name' => 'design_upload_theme',
'type' => 'file'));
$this->out->element('p', 'form_guide', _('You can upload a custom StatusNet theme as a .ZIP archive.'));
$this->unli();
}
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
function showBackground()
{
$design = $this->out->design;
$this->out->elementStart('fieldset', array('id' =>
......@@ -483,6 +551,11 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
function showColors()
{
$design = $this->out->design;
$this->out->elementStart('fieldset', array('id' => 'settings_design_color'));
$this->out->element('legend', null, _('Change colours'));
......@@ -490,6 +563,7 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementStart('ul', 'form_data');
try {
// @fixme avoid loop unrolling in non-performance-critical contexts like this
$bgcolor = new WebColor($design->backgroundcolor);
......@@ -557,6 +631,7 @@ class DesignAdminPanelForm extends AdminForm
$this->unli();
} catch (WebColorException $e) {
// @fixme normalize them individually!
common_log(LOG_ERR, 'Bad color values in site design: ' .
$e->getMessage());
}
......@@ -566,6 +641,27 @@ class DesignAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
}
function showAdvanced()
{
if (common_config('custom_css', 'enabled')) {
$this->out->elementStart('fieldset', array('id' => 'settings_design_advanced'));
$this->out->element('legend', null, _('Advanced'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->out->element('label', array('for' => 'css'), _('Custom CSS'));
$this->out->element('textarea', array('name' => 'css',
'id' => 'css',
'cols' => '50',
'rows' => '10'),
strval(common_config('custom_css', 'css')));
$this->unli();
$this->out->elementEnd('fieldset');
$this->out->elementEnd('ul');
}
}
/**
* Action elements
*
......
......@@ -1863,4 +1863,16 @@ class Notice extends Memcached_DataObject
return $ns;
}
/**
* Determine whether the notice was locally created
*
* @return boolean locality
*/
public function isLocal()
{
return ($this->is_local == Notice::LOCAL_PUBLIC ||
$this->is_local == Notice::LOCAL_NONPUBLIC);
}
}
......@@ -149,21 +149,15 @@ class Status_network extends Safe_DataObject
$this->decache(); # while we still have the values!
return parent::delete();
}
/**
* @param string $servername hostname
* @param string $pathname URL base path
* @param string $wildcard hostname suffix to match wildcard config
* @return mixed Status_network or null
*/
static function setupSite($servername, $pathname, $wildcard)
static function getFromHostname($servername, $wildcard)
{
global $config;
$sn = null;
// XXX I18N, probably not crucial for hostnames
// XXX This probably needs a tune up
if (0 == strncasecmp(strrev($wildcard), strrev($servername), strlen($wildcard))) {
// special case for exact match
if (0 == strcasecmp($servername, $wildcard)) {
......@@ -182,6 +176,23 @@ class Status_network extends Safe_DataObject
}
}
}
return $sn;
}
/**
* @param string $servername hostname
* @param string $pathname URL base path
* @param string $wildcard hostname suffix to match wildcard config
*/
static function setupSite($servername, $pathname, $wildcard)
{
global $config;
$sn = null;
// XXX I18N, probably not crucial for hostnames
// XXX This probably needs a tune up
$sn = self::getFromHostname($servername, $wildcard);
if (!empty($sn)) {
......
......@@ -233,6 +233,16 @@ class Action extends HTMLOutputter // lawsuit
Event::handle('EndShowDesign', array($this));
}
Event::handle('EndShowStyles', array($this));
if (common_config('custom_css', 'enabled')) {
$css = common_config('custom_css', 'css');
if (Event::handle('StartShowCustomCss', array($this, &$css))) {
if (trim($css) != '') {
$this->style($css);
}
Event::handle('EndShowCustomCss', array($this));
}
}
}
}
......
......@@ -283,9 +283,10 @@ class AdminPanelAction extends Action
$this->clientError(_("Unable to delete design setting."));
return null;
}
return $result;
}
return $result;
return null;
}
function canAdmin($name)
......
......@@ -141,10 +141,17 @@ $default =
'dir' => null,
'path'=> null,
'ssl' => null),
'theme_upload' =>
array('enabled' => extension_loaded('zip')),
'javascript' =>
array('server' => null,
'path'=> null,
'ssl' => null),
'local' => // To override path/server for themes in 'local' dir (not currently applied to local plugins)
array('server' => null,
'dir' => null,
'path' => null,
'ssl' => null),
'throttle' =>
array('enabled' => false, // whether to throttle edits; false by default
'count' => 20, // number of allowed messages in timespan
......@@ -260,6 +267,9 @@ $default =
'linkcolor' => null,
'backgroundimage' => null,
'disposition' => null),
'custom_css' =>
array('enabled' => true,
'css' => ''),
'notice' =>
array('contentlimit' => null),
'message' =>
......
......@@ -147,5 +147,30 @@ class LiberalStomp extends Stomp
}
return $frame;
}
}
/**
* Write frame to server
*
* @param StompFrame $stompFrame
*/
protected function _writeFrame (StompFrame $stompFrame)
{
if (!is_resource($this->_socket)) {
require_once 'Stomp/Exception.php';
throw new StompException('Socket connection hasn\'t been established');
}
$data = $stompFrame->__toString();
// Make sure the socket's in a writable state; if not, wait a bit.
stream_set_blocking($this->_socket, 1);
$r = fwrite($this->_socket, $data, strlen($data));
stream_set_blocking($this->_socket, 0);
if ($r === false || $r == 0) {
$this->_reconnect();
$this->_writeFrame($stompFrame);
}
}
}
......@@ -115,11 +115,12 @@ class StompQueueManager extends QueueManager
*
* @param mixed $object
* @param string $queue
* @param string $siteNickname optional override to drop into another site's queue
*
* @return boolean true on success
* @throws StompException on connection or send error
*/
public function enqueue($object, $queue)
public function enqueue($object, $queue, $siteNickname=null)
{
$this->_connect();
if (common_config('queue', 'stomp_enqueue_on')) {
......@@ -134,7 +135,7 @@ class StompQueueManager extends QueueManager
} else {
$idx = $this->defaultIdx;
}
return $this->_doEnqueue($object, $queue, $idx);
return $this->_doEnqueue($object, $queue, $idx, $siteNickname);
}
/**
......@@ -144,10 +145,10 @@ class StompQueueManager extends QueueManager
* @return boolean true on success
* @throws StompException on connection or send error
*/
protected function _doEnqueue($object, $queue, $idx)
protected function _doEnqueue($object, $queue, $idx, $siteNickname=null)
{
$rep = $this->logrep($object);
$envelope = array('site' => common_config('site', 'nickname'),
$envelope = array('site' => $siteNickname ? $siteNickname : common_config('site', 'nickname'),
'handler' => $queue,
'payload' => $this->encode($object));
$msg = serialize($envelope);
......
......@@ -38,6 +38,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* Themes are directories with some expected sub-directories and files
* in them. They're found in either local/theme (for locally-installed themes)
* or theme/ subdir of installation dir.
*
* Note that the 'local' directory can be overridden as $config['local']['path']
* and $config['local']['dir'] etc.
*
* This used to be a couple of functions, but for various reasons it's nice
* to have a class instead.
......@@ -76,7 +79,7 @@ class Theme
if (file_exists($fulldir) && is_dir($fulldir)) {
$this->dir = $fulldir;
$this->path = common_path('local/theme/'.$name.'/');
$this->path = $this->relativeThemePath('local', 'local', 'theme/' . $name);
return;
}
......@@ -89,42 +92,63 @@ class Theme
if (file_exists($fulldir) && is_dir($fulldir)) {
$this->dir = $fulldir;
$this->path = $this->relativeThemePath('theme', 'theme', $name);
}
}
$path = common_config('theme', 'path');
/**
* Build a full URL to the given theme's base directory, possibly
* using an offsite theme server path.
*
* @param string $group configuration section name to pull paths from
* @param string $fallbackSubdir default subdirectory under INSTALLDIR
* @param string $name theme name
*
* @return string URL
*
* @todo consolidate code with that for other customizable paths
*/
if (empty($path)) {
$path = common_config('site', 'path') . '/theme/';
}
protected function relativeThemePath($group, $fallbackSubdir, $name)
{
$path = common_config($group, 'path');
if ($path[strlen($path)-1] != '/') {
$path .= '/';
if (empty($path)) {
$path = common_config('site', 'path') . '/';
if ($fallbackSubdir) {
$path .= $fallbackSubdir . '/';
}
}
if ($path[0] != '/') {
$path = '/'.$path;
}
if ($path[strlen($path)-1] != '/') {
$path .= '/';
}
$server = common_config('theme', 'server');
if ($path[0] != '/') {
$path = '/'.$path;
}
if (empty($server)) {
$server = common_config('site', 'server');
}
$server = common_config($group, 'server');
$ssl = common_config('theme', 'ssl');
if (empty($server)) {
$server = common_config('site', 'server');
}
if (is_null($ssl)) { // null -> guess
if (common_config('site', 'ssl') == 'always' &&
!common_config('theme', 'server')) {
$ssl = true;
} else {
$ssl = false;
}
$ssl = common_config($group, 'ssl');
if (is_null($ssl)) { // null -> guess
if (common_config('site', 'ssl') == 'always' &&
!common_config($group, 'server')) {
$ssl = true;
} else {
$ssl = false;
}
}
$protocol = ($ssl) ? 'https' : 'http';
$protocol = ($ssl) ? 'https' : 'http';
$this->path = $protocol . '://'.$server.$path.$name;
}
$path = $protocol . '://'.$server.$path.$name;
return $path;
}
/**
......@@ -236,7 +260,13 @@ class Theme
protected static function localRoot()
{
return INSTALLDIR.'/local/theme';
$basedir = common_config('local', 'dir');
if (empty($basedir)) {
$basedir = INSTALLDIR . '/local';
}
return $basedir . '/theme';
}
/**
......
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Utilities for theme files and paths
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Paths
* @package StatusNet
* @author Brion Vibber <brion@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Encapsulation of the validation-and-save process when dealing with
* a user-uploaded StatusNet theme archive...
*
* @todo extract theme metadata from css/display.css
* @todo allow saving multiple themes
*/
class ThemeUploader
{
protected $sourceFile;
protected $isUpload;
private $prevErrorReporting;
public function __construct($filename)
{
if (!class_exists('ZipArchive')) {
throw new Exception(_("This server cannot handle theme uploads without ZIP support."));
}
$this->sourceFile = $filename;
}
public static function fromUpload($name)
{
if (!isset($_FILES[$name]['error'])) {
throw new ServerException(_("Theme upload missing or failed."));
}
if ($_FILES[$name]['error'] != UPLOAD_ERR_OK) {
throw new ServerException(_("Theme upload missing or failed."));
}
return new ThemeUploader($_FILES[$name]['tmp_name']);
}
/**
* @param string $destDir
* @throws Exception on bogus files
*/
public function extract($destDir)
{
$zip = $this->openArchive();
// First pass: validate but don't save anything to disk.
// Any errors will trip an exception.
$this->traverseArchive($zip);
// Second pass: now that we know we're good, actually extract!
$tmpDir = $destDir . '.tmp' . getmypid();
$this->traverseArchive($zip, $tmpDir);
$zip->close();
if (file_exists($destDir)) {
$killDir = $tmpDir . '.old';
$this->quiet();
$ok = rename($destDir, $killDir);
$this->loud();
if (!$ok) {
common_log(LOG_ERR, "Could not move old custom theme from $destDir to $killDir");
throw new ServerException(_("Failed saving theme."));
}
} else {
$killDir = false;
}
$this->quiet();
$ok = rename($tmpDir, $destDir);
$this->loud();
if (!$ok) {
common_log(LOG_ERR, "Could not move saved theme from $tmpDir to $destDir");
throw new ServerException(_("Failed saving theme."));
}
if ($killDir) {
$this->recursiveRmdir($killDir);
}
}
/**
*
*/
protected function traverseArchive($zip, $outdir=false)
{
$sizeLimit = 2 * 1024 * 1024; // 2 megabyte space limit?
$blockSize = 4096; // estimated; any entry probably takes this much space