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 // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit.
124 // TRANS: %s is the number of bytes of the CONTENT_LENGTH.
125 $msg = _m('The server was unable to handle that much POST data (%s byte) due to its current configuration.',
126 'The server was unable to handle that much POST data (%s bytes) due to its current configuration.',
127 intval($_SERVER['CONTENT_LENGTH']));
128 $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
132 // check for file uploads
134 $bgimage = $this->saveBackgroundImage();
135 $customTheme = $this->saveCustomTheme();
137 $oldtheme = common_config('site', 'theme');
139 // This feels pretty hacky :D
140 $this->args['theme'] = $customTheme;
141 $themeChanged = true;
143 $themeChanged = ($this->trimmed('theme') != $oldtheme);
146 static $settings = array('theme', 'logo', 'ssllogo');
150 foreach ($settings as $setting) {
151 $values[$setting] = $this->trimmed($setting);
154 $this->validate($values);
156 $config = new Config();
158 $config->query('BEGIN');
161 // If the theme has changed, reset custom colors and let them pick
162 // up the new theme's defaults.
163 $colors = array('background', 'content', 'sidebar', 'text', 'link');
164 foreach ($colors as $colorKey) {
165 // Clear from global config so we see defaults on this page...
166 $GLOBALS['config']['design'][$colorKey . 'color'] = false;
168 // And remove old settings from DB...
169 $this->deleteSetting('design', $colorKey . 'color');
172 // Only save colors from the form if the theme has not changed.
174 // @fixme a future more ajaxy form should allow theme switch
175 // and color customization in one step.
177 $bgcolor = new WebColor($this->trimmed('design_background'));
178 $ccolor = new WebColor($this->trimmed('design_content'));
179 $sbcolor = new WebColor($this->trimmed('design_sidebar'));
180 $tcolor = new WebColor($this->trimmed('design_text'));
181 $lcolor = new WebColor($this->trimmed('design_links'));
183 Config::save('design', 'backgroundcolor', $bgcolor->intValue());
184 Config::save('design', 'contentcolor', $ccolor->intValue());
185 Config::save('design', 'sidebarcolor', $sbcolor->intValue());
186 Config::save('design', 'textcolor', $tcolor->intValue());
187 Config::save('design', 'linkcolor', $lcolor->intValue());
190 $onoff = $this->arg('design_background-image_onoff');
195 if ($onoff == 'on') {
201 $tile = $this->boolean('design_background-image_repeat');
203 // Hack to use Design's bit setter
204 $scratch = new Design();
205 $scratch->setDisposition($on, $off, $tile);
207 Config::save('design', 'disposition', $scratch->disposition);
209 foreach ($settings as $setting) {
210 Config::save('site', $setting, $values[$setting]);
213 if (isset($bgimage)) {
214 Config::save('design', 'backgroundimage', $bgimage);
217 if (common_config('custom_css', 'enabled')) {
218 $css = $this->arg('css');
219 if ($css != common_config('custom_css', 'css')) {
220 Config::save('custom_css', 'css', $css);
224 $config->query('COMMIT');
228 * Restore the default design
233 function restoreDefaults()
235 $this->deleteSetting('site', 'logo');
236 $this->deleteSetting('site', 'ssllogo');
237 $this->deleteSetting('site', 'theme');
240 'theme', 'backgroundimage', 'backgroundcolor', 'contentcolor',
241 'sidebarcolor', 'textcolor', 'linkcolor', 'disposition'
244 foreach ($settings as $setting) {
245 $this->deleteSetting('design', $setting);
248 // XXX: Should we restore the default dir settings, etc.? --Z
250 // XXX: I can't get it to show the new settings without forcing
251 // this terrible reload -- FIX ME!
252 common_redirect(common_local_url('designadminpanel'), 303);
256 * Save the background image if the user uploaded one
258 * @return string $filename the filename of the image
261 function saveBackgroundImage()
264 if (isset($_FILES['design_background-image_file']['error']) &&
265 $_FILES['design_background-image_file']['error'] ==
272 ImageFile::fromUpload('design_background-image_file');
273 } catch (Exception $e) {
274 $this->clientError('Unable to save background image.');
278 // Note: site design background image has a special filename
280 $filename = Design::filename('site-design-background',
281 image_type_to_extension($imagefile->type),
284 $filepath = Design::path($filename);
286 move_uploaded_file($imagefile->filepath, $filepath);
288 // delete any old backround img laying around
290 if (isset($this->design->backgroundimage)) {
291 @unlink(Design::path($design->backgroundimage));
299 * Save the custom theme if the user uploaded one.
301 * @return mixed custom theme name, if succesful, or null if no theme upload.
302 * @throws ClientException for invalid theme archives
303 * @throws ServerException if trouble saving the theme files
306 function saveCustomTheme()
308 if (common_config('theme_upload', 'enabled') &&
309 $_FILES['design_upload_theme']['error'] == UPLOAD_ERR_OK) {
311 $upload = ThemeUploader::fromUpload('design_upload_theme');
312 $basedir = common_config('local', 'dir');
313 if (empty($basedir)) {
314 $basedir = INSTALLDIR . '/local';
316 $name = 'custom'; // @todo allow multiples, custom naming?
317 $outdir = $basedir . '/theme/' . $name;
318 $upload->extract($outdir);
326 * Attempt to validate setting values
331 function validate(&$values)
333 if (!empty($values['logo']) &&
334 !Validate::uri($values['logo'], array('allowed_schemes' => array('http', 'https')))) {
335 $this->clientError(_('Invalid logo URL.'));
338 if (!empty($values['ssllogo']) &&
339 !Validate::uri($values['ssllogo'], array('allowed_schemes' => array('https')))) {
340 $this->clientError(_('Invalid SSL logo URL.'));
343 if (!in_array($values['theme'], Theme::listAvailable())) {
344 $this->clientError(sprintf(_("Theme not available: %s."), $values['theme']));
349 * Add the Farbtastic stylesheet
354 function showStylesheets()
356 parent::showStylesheets();
357 $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv');
361 * Add the Farbtastic scripts
366 function showScripts()
368 parent::showScripts();
370 $this->script('farbtastic/farbtastic.js');
371 $this->script('userdesign.go.js');
373 $this->autofocus('design_background-image_file');
378 class DesignAdminPanelForm extends AdminForm
384 * @return int ID of the form
389 return 'form_design_admin_panel';
395 * @return string class of the form
400 return 'form_settings';
404 * HTTP method used to submit the form
406 * For image data we need to send multipart/form-data
407 * so we set that here too
409 * @return string the method to use for submitting
414 $this->enctype = 'multipart/form-data';
422 * @return string URL of the action
427 return common_local_url('designadminpanel');
431 * Data elements of the form
440 $this->showBackground();
442 $this->showAdvanced();
447 $this->out->elementStart('fieldset', array('id' => 'settings_design_logo'));
448 $this->out->element('legend', null, _('Change logo'));
450 $this->out->elementStart('ul', 'form_data');
453 $this->input('logo', _('Site logo'), 'Logo for the site (full URL)');
457 $this->input('ssllogo', _('SSL logo'), 'Logo to show on SSL pages');
460 $this->out->elementEnd('ul');
462 $this->out->elementEnd('fieldset');
468 $this->out->elementStart('fieldset', array('id' => 'settings_design_theme'));
469 $this->out->element('legend', null, _('Change theme'));
471 $this->out->elementStart('ul', 'form_data');
473 $themes = Theme::listAvailable();
475 // XXX: listAvailable() can return an empty list if you
476 // screw up your settings, so just in case:
478 if (empty($themes)) {
479 $themes = array('default', 'default');
483 $themes = array_combine($themes, $themes);
486 $this->out->dropdown('theme', _('Site theme'),
487 $themes, _('Theme for the site.'),
488 false, $this->value('theme'));
491 if (common_config('theme_upload', 'enabled')) {
493 $this->out->element('label', array('for' => 'design_upload_theme'), _('Custom theme'));
494 $this->out->element('input', array('id' => 'design_upload_theme',
495 'name' => 'design_upload_theme',
497 $this->out->element('p', 'form_guide', _('You can upload a custom StatusNet theme as a .ZIP archive.'));
501 $this->out->elementEnd('ul');
503 $this->out->elementEnd('fieldset');
506 function showBackground()
508 $design = $this->out->design;
510 $this->out->elementStart('fieldset', array('id' =>
511 'settings_design_background-image'));
512 $this->out->element('legend', null, _('Change background image'));
513 $this->out->elementStart('ul', 'form_data');
516 $this->out->element('label', array('for' => 'design_background-image_file'),
518 $this->out->element('input', array('name' => 'design_background-image_file',
520 'id' => 'design_background-image_file'));
521 $this->out->element('p', 'form_guide',
522 sprintf(_('You can upload a background image for the site. ' .
523 'The maximum file size is %1$s.'), ImageFile::maxFileSize()));
524 $this->out->element('input', array('name' => 'MAX_FILE_SIZE',
526 'id' => 'MAX_FILE_SIZE',
527 'value' => ImageFile::maxFileSizeInt()));
530 if (!empty($design->backgroundimage)) {
532 $this->out->elementStart('li', array('id' =>
533 'design_background-image_onoff'));
535 $this->out->element('img', array('src' =>
536 Design::url($design->backgroundimage)));
538 $attrs = array('name' => 'design_background-image_onoff',
540 'id' => 'design_background-image_on',
544 if ($design->disposition & BACKGROUND_ON) {
545 $attrs['checked'] = 'checked';
548 $this->out->element('input', $attrs);
550 $this->out->element('label', array('for' => 'design_background-image_on',
552 // TRANS: Used as radio button label to add a background image.
555 $attrs = array('name' => 'design_background-image_onoff',
557 'id' => 'design_background-image_off',
561 if ($design->disposition & BACKGROUND_OFF) {
562 $attrs['checked'] = 'checked';
565 $this->out->element('input', $attrs);
567 $this->out->element('label', array('for' => 'design_background-image_off',
569 // TRANS: Used as radio button label to not add a background image.
571 $this->out->element('p', 'form_guide', _('Turn background image on or off.'));
575 $this->out->checkbox('design_background-image_repeat',
576 _('Tile background image'),
577 ($design->disposition & BACKGROUND_TILE) ? true : false);
581 $this->out->elementEnd('ul');
582 $this->out->elementEnd('fieldset');
585 function showColors()
587 $design = $this->out->design;
589 $this->out->elementStart('fieldset', array('id' => 'settings_design_color'));
590 $this->out->element('legend', null, _('Change colours'));
592 $this->out->elementStart('ul', 'form_data');
595 // @fixme avoid loop unrolling in non-performance-critical contexts like this
597 $bgcolor = new WebColor($design->backgroundcolor);
600 $this->out->element('label', array('for' => 'swatch-1'), _('Background'));
601 $this->out->element('input', array('name' => 'design_background',
610 $ccolor = new WebColor($design->contentcolor);
613 $this->out->element('label', array('for' => 'swatch-2'), _('Content'));
614 $this->out->element('input', array('name' => 'design_content',
623 $sbcolor = new WebColor($design->sidebarcolor);
626 $this->out->element('label', array('for' => 'swatch-3'), _('Sidebar'));
627 $this->out->element('input', array('name' => 'design_sidebar',
636 $tcolor = new WebColor($design->textcolor);
639 $this->out->element('label', array('for' => 'swatch-4'), _('Text'));
640 $this->out->element('input', array('name' => 'design_text',
649 $lcolor = new WebColor($design->linkcolor);
652 $this->out->element('label', array('for' => 'swatch-5'), _('Links'));
653 $this->out->element('input', array('name' => 'design_links',
662 } catch (WebColorException $e) {
663 // @fixme normalize them individually!
664 common_log(LOG_ERR, 'Bad color values in site design: ' .
668 $this->out->elementEnd('fieldset');
670 $this->out->elementEnd('ul');
673 function showAdvanced()
675 if (common_config('custom_css', 'enabled')) {
676 $this->out->elementStart('fieldset', array('id' => 'settings_design_advanced'));
677 $this->out->element('legend', null, _('Advanced'));
678 $this->out->elementStart('ul', 'form_data');
681 $this->out->element('label', array('for' => 'css'), _('Custom CSS'));
682 $this->out->element('textarea', array('name' => 'css',
686 strval(common_config('custom_css', 'css')));
689 $this->out->elementEnd('fieldset');
690 $this->out->elementEnd('ul');
700 function formActions()
702 $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default',
703 'defaults', _('Restore default designs'));
705 $this->out->element('input', array('id' => 'settings_design_reset',
708 'class' => 'submit form_action-primary',
709 'title' => _('Reset back to default')));
711 $this->out->submit('save', _('Save'), 'submit form_action-secondary',
712 'save', _('Save design'));