3 * StatusNet, the distributed open-source microblogging tool
5 * Design administration panel
9 * LICENCE: This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 * @author Evan Prodromou <evan@status.net>
25 * @author Zach Copley <zach@status.net>
26 * @author Sarven Capadisli <csarven@status.net>
27 * @copyright 2008-2009 StatusNet, Inc.
28 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
29 * @link http://status.net/
32 if (!defined('STATUSNET')) {
37 * Administer design settings
41 * @author Evan Prodromou <evan@status.net>
42 * @author Zach Copley <zach@status.net>
43 * @author Sarven Capadisli <csarven@status.net>
44 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
45 * @link http://status.net/
48 class DesignadminpanelAction extends AdminPanelAction
51 /* The default site design */
55 * Returns the page title
57 * @return string page title
62 // TRANS: Message used as title for design settings for the site.
67 * Instructions for using this form.
69 * @return string instructions
72 function getInstructions()
74 return _('Design settings for this StatusNet site');
78 * Get the default design and show the design admin panel form
85 $this->design = Design::siteDesign();
86 $form = new DesignAdminPanelForm($this);
92 * Save settings from the form
97 function saveSettings()
99 if ($this->arg('save')) {
100 $this->saveDesignSettings();
101 } else if ($this->arg('defaults')) {
102 $this->restoreDefaults();
104 $this->clientError(_('Unexpected form submission.'));
109 * Save the new design settings
114 function saveDesignSettings()
116 // Workaround for PHP returning empty $_POST and $_FILES when POST
117 // length > post_max_size in php.ini
121 && ($_SERVER['CONTENT_LENGTH'] > 0)
123 $msg = _('The server was unable to handle that much POST ' .
124 'data (%s bytes) due to its current configuration.');
125 $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
129 // check for file uploads
131 $bgimage = $this->saveBackgroundImage();
132 $customTheme = $this->saveCustomTheme();
134 $oldtheme = common_config('site', 'theme');
136 // This feels pretty hacky :D
137 $this->args['theme'] = $customTheme;
138 $themeChanged = true;
140 $themeChanged = ($this->trimmed('theme') != $oldtheme);
143 static $settings = array('theme', 'logo', 'ssllogo');
147 foreach ($settings as $setting) {
148 $values[$setting] = $this->trimmed($setting);
151 $this->validate($values);
153 $config = new Config();
155 $config->query('BEGIN');
158 // If the theme has changed, reset custom colors and let them pick
159 // up the new theme's defaults.
160 $colors = array('background', 'content', 'sidebar', 'text', 'link');
161 foreach ($colors as $colorKey) {
162 // Clear from global config so we see defaults on this page...
163 $GLOBALS['config']['design'][$colorKey . 'color'] = false;
165 // And remove old settings from DB...
166 $this->deleteSetting('design', $colorKey . 'color');
169 // Only save colors from the form if the theme has not changed.
171 // @fixme a future more ajaxy form should allow theme switch
172 // and color customization in one step.
174 $bgcolor = new WebColor($this->trimmed('design_background'));
175 $ccolor = new WebColor($this->trimmed('design_content'));
176 $sbcolor = new WebColor($this->trimmed('design_sidebar'));
177 $tcolor = new WebColor($this->trimmed('design_text'));
178 $lcolor = new WebColor($this->trimmed('design_links'));
180 Config::save('design', 'backgroundcolor', $bgcolor->intValue());
181 Config::save('design', 'contentcolor', $ccolor->intValue());
182 Config::save('design', 'sidebarcolor', $sbcolor->intValue());
183 Config::save('design', 'textcolor', $tcolor->intValue());
184 Config::save('design', 'linkcolor', $lcolor->intValue());
187 $onoff = $this->arg('design_background-image_onoff');
192 if ($onoff == 'on') {
198 $tile = $this->boolean('design_background-image_repeat');
200 // Hack to use Design's bit setter
201 $scratch = new Design();
202 $scratch->setDisposition($on, $off, $tile);
204 Config::save('design', 'disposition', $scratch->disposition);
206 foreach ($settings as $setting) {
207 Config::save('site', $setting, $values[$setting]);
210 if (isset($bgimage)) {
211 Config::save('design', 'backgroundimage', $bgimage);
214 if (common_config('custom_css', 'enabled')) {
215 $css = $this->arg('css');
216 if ($css != common_config('custom_css', 'css')) {
217 Config::save('custom_css', 'css', $css);
221 $config->query('COMMIT');
225 * Restore the default design
230 function restoreDefaults()
232 $this->deleteSetting('site', 'logo');
233 $this->deleteSetting('site', 'ssllogo');
234 $this->deleteSetting('site', 'theme');
237 'theme', 'backgroundimage', 'backgroundcolor', 'contentcolor',
238 'sidebarcolor', 'textcolor', 'linkcolor', 'disposition'
241 foreach ($settings as $setting) {
242 $this->deleteSetting('design', $setting);
245 // XXX: Should we restore the default dir settings, etc.? --Z
247 // XXX: I can't get it to show the new settings without forcing
248 // this terrible reload -- FIX ME!
249 common_redirect(common_local_url('designadminpanel'), 303);
253 * Save the background image if the user uploaded one
255 * @return string $filename the filename of the image
258 function saveBackgroundImage()
261 if (isset($_FILES['design_background-image_file']['error']) &&
262 $_FILES['design_background-image_file']['error'] ==
269 ImageFile::fromUpload('design_background-image_file');
270 } catch (Exception $e) {
271 $this->clientError('Unable to save background image.');
275 // Note: site design background image has a special filename
277 $filename = Design::filename('site-design-background',
278 image_type_to_extension($imagefile->type),
281 $filepath = Design::path($filename);
283 move_uploaded_file($imagefile->filepath, $filepath);
285 // delete any old backround img laying around
287 if (isset($this->design->backgroundimage)) {
288 @unlink(Design::path($design->backgroundimage));
296 * Save the custom theme if the user uploaded one.
298 * @return mixed custom theme name, if succesful, or null if no theme upload.
299 * @throws ClientException for invalid theme archives
300 * @throws ServerException if trouble saving the theme files
303 function saveCustomTheme()
305 if (common_config('theme_upload', 'enabled') &&
306 $_FILES['design_upload_theme']['error'] == UPLOAD_ERR_OK) {
308 $upload = ThemeUploader::fromUpload('design_upload_theme');
309 $basedir = common_config('local', 'dir');
310 if (empty($basedir)) {
311 $basedir = INSTALLDIR . '/local';
313 $name = 'custom'; // @todo allow multiples, custom naming?
314 $outdir = $basedir . '/theme/' . $name;
315 $upload->extract($outdir);
323 * Attempt to validate setting values
328 function validate(&$values)
330 if (!empty($values['logo']) &&
331 !Validate::uri($values['logo'], array('allowed_schemes' => array('http', 'https')))) {
332 $this->clientError(_('Invalid logo URL.'));
335 if (!empty($values['ssllogo']) &&
336 !Validate::uri($values['ssllogo'], array('allowed_schemes' => array('https')))) {
337 $this->clientError(_('Invalid SSL logo URL.'));
340 if (!in_array($values['theme'], Theme::listAvailable())) {
341 $this->clientError(sprintf(_("Theme not available: %s."), $values['theme']));
346 * Add the Farbtastic stylesheet
351 function showStylesheets()
353 parent::showStylesheets();
354 $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv');
358 * Add the Farbtastic scripts
363 function showScripts()
365 parent::showScripts();
367 $this->script('farbtastic/farbtastic.js');
368 $this->script('userdesign.go.js');
370 $this->autofocus('design_background-image_file');
375 class DesignAdminPanelForm extends AdminForm
381 * @return int ID of the form
386 return 'form_design_admin_panel';
392 * @return string class of the form
397 return 'form_settings';
401 * HTTP method used to submit the form
403 * For image data we need to send multipart/form-data
404 * so we set that here too
406 * @return string the method to use for submitting
411 $this->enctype = 'multipart/form-data';
419 * @return string URL of the action
424 return common_local_url('designadminpanel');
428 * Data elements of the form
437 $this->showBackground();
439 $this->showAdvanced();
444 $this->out->elementStart('fieldset', array('id' => 'settings_design_logo'));
445 $this->out->element('legend', null, _('Change logo'));
447 $this->out->elementStart('ul', 'form_data');
450 $this->input('logo', _('Site logo'), 'Logo for the site (full URL)');
454 $this->input('ssllogo', _('SSL logo'), 'Logo to show on SSL pages');
457 $this->out->elementEnd('ul');
459 $this->out->elementEnd('fieldset');
465 $this->out->elementStart('fieldset', array('id' => 'settings_design_theme'));
466 $this->out->element('legend', null, _('Change theme'));
468 $this->out->elementStart('ul', 'form_data');
470 $themes = Theme::listAvailable();
472 // XXX: listAvailable() can return an empty list if you
473 // screw up your settings, so just in case:
475 if (empty($themes)) {
476 $themes = array('default', 'default');
480 $themes = array_combine($themes, $themes);
483 $this->out->dropdown('theme', _('Site theme'),
484 $themes, _('Theme for the site.'),
485 false, $this->value('theme'));
488 if (common_config('theme_upload', 'enabled')) {
490 $this->out->element('label', array('for' => 'design_upload_theme'), _('Custom theme'));
491 $this->out->element('input', array('id' => 'design_upload_theme',
492 'name' => 'design_upload_theme',
494 $this->out->element('p', 'form_guide', _('You can upload a custom StatusNet theme as a .ZIP archive.'));
498 $this->out->elementEnd('ul');
500 $this->out->elementEnd('fieldset');
503 function showBackground()
505 $design = $this->out->design;
507 $this->out->elementStart('fieldset', array('id' =>
508 'settings_design_background-image'));
509 $this->out->element('legend', null, _('Change background image'));
510 $this->out->elementStart('ul', 'form_data');
513 $this->out->element('label', array('for' => 'design_background-image_file'),
515 $this->out->element('input', array('name' => 'design_background-image_file',
517 'id' => 'design_background-image_file'));
518 $this->out->element('p', 'form_guide',
519 sprintf(_('You can upload a background image for the site. ' .
520 'The maximum file size is %1$s.'), ImageFile::maxFileSize()));
521 $this->out->element('input', array('name' => 'MAX_FILE_SIZE',
523 'id' => 'MAX_FILE_SIZE',
524 'value' => ImageFile::maxFileSizeInt()));
527 if (!empty($design->backgroundimage)) {
529 $this->out->elementStart('li', array('id' =>
530 'design_background-image_onoff'));
532 $this->out->element('img', array('src' =>
533 Design::url($design->backgroundimage)));
535 $attrs = array('name' => 'design_background-image_onoff',
537 'id' => 'design_background-image_on',
541 if ($design->disposition & BACKGROUND_ON) {
542 $attrs['checked'] = 'checked';
545 $this->out->element('input', $attrs);
547 $this->out->element('label', array('for' => 'design_background-image_on',
549 // TRANS: Used as radio button label to add a background image.
552 $attrs = array('name' => 'design_background-image_onoff',
554 'id' => 'design_background-image_off',
558 if ($design->disposition & BACKGROUND_OFF) {
559 $attrs['checked'] = 'checked';
562 $this->out->element('input', $attrs);
564 $this->out->element('label', array('for' => 'design_background-image_off',
566 // TRANS: Used as radio button label to not add a background image.
568 $this->out->element('p', 'form_guide', _('Turn background image on or off.'));
572 $this->out->checkbox('design_background-image_repeat',
573 _('Tile background image'),
574 ($design->disposition & BACKGROUND_TILE) ? true : false);
578 $this->out->elementEnd('ul');
579 $this->out->elementEnd('fieldset');
582 function showColors()
584 $design = $this->out->design;
586 $this->out->elementStart('fieldset', array('id' => 'settings_design_color'));
587 $this->out->element('legend', null, _('Change colours'));
589 $this->out->elementStart('ul', 'form_data');
592 // @fixme avoid loop unrolling in non-performance-critical contexts like this
594 $bgcolor = new WebColor($design->backgroundcolor);
597 $this->out->element('label', array('for' => 'swatch-1'), _('Background'));
598 $this->out->element('input', array('name' => 'design_background',
607 $ccolor = new WebColor($design->contentcolor);
610 $this->out->element('label', array('for' => 'swatch-2'), _('Content'));
611 $this->out->element('input', array('name' => 'design_content',
620 $sbcolor = new WebColor($design->sidebarcolor);
623 $this->out->element('label', array('for' => 'swatch-3'), _('Sidebar'));
624 $this->out->element('input', array('name' => 'design_sidebar',
633 $tcolor = new WebColor($design->textcolor);
636 $this->out->element('label', array('for' => 'swatch-4'), _('Text'));
637 $this->out->element('input', array('name' => 'design_text',
646 $lcolor = new WebColor($design->linkcolor);
649 $this->out->element('label', array('for' => 'swatch-5'), _('Links'));
650 $this->out->element('input', array('name' => 'design_links',
659 } catch (WebColorException $e) {
660 // @fixme normalize them individually!
661 common_log(LOG_ERR, 'Bad color values in site design: ' .
665 $this->out->elementEnd('fieldset');
667 $this->out->elementEnd('ul');
670 function showAdvanced()
672 if (common_config('custom_css', 'enabled')) {
673 $this->out->elementStart('fieldset', array('id' => 'settings_design_advanced'));
674 $this->out->element('legend', null, _('Advanced'));
675 $this->out->elementStart('ul', 'form_data');
678 $this->out->element('label', array('for' => 'css'), _('Custom CSS'));
679 $this->out->element('textarea', array('name' => 'css',
683 strval(common_config('custom_css', 'css')));
686 $this->out->elementEnd('fieldset');
687 $this->out->elementEnd('ul');
697 function formActions()
699 $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default',
700 'defaults', _('Restore default designs'));
702 $this->out->element('input', array('id' => 'settings_design_reset',
705 'class' => 'submit form_action-primary',
706 'title' => _('Reset back to default')));
708 $this->out->submit('save', _('Save'), 'submit form_action-secondary',
709 'save', _('Save design'));