Textpattern | PHP Cross Reference | Content Management Systems |
Description: Styles panel.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * https://textpattern.com/ 6 * 7 * Copyright (C) 2020 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <https://www.gnu.org/licenses/>. 22 */ 23 24 /** 25 * Styles panel. 26 * 27 * @package Admin\CSS 28 */ 29 30 use Textpattern\Skin\Skin; 31 use Textpattern\Skin\Css; 32 33 if (!defined('txpinterface')) { 34 die('txpinterface is undefined.'); 35 } 36 37 if ($event == 'css') { 38 require_privs('css'); 39 40 $instance = Txp::get('Textpattern\Skin\Css'); 41 42 bouncer($step, array( 43 'pour' => false, 44 'css_save' => true, 45 'css_delete' => true, 46 'css_edit' => false, 47 'css_skin_change' => true, 48 )); 49 50 switch (strtolower($step)) { 51 case '': 52 css_edit(); 53 break; 54 case 'pour': 55 css_edit(); 56 break; 57 case 'css_save': 58 css_save(); 59 break; 60 case 'css_delete': 61 css_delete(); 62 break; 63 case 'css_edit': 64 css_edit(); 65 break; 66 case "css_skin_change": 67 Txp::get('Textpattern\Skin\Css')->selectEdit(); 68 css_edit(); 69 break; 70 } 71 } 72 73 /** 74 * Renders a list of stylesheets. 75 * 76 * @param array $current Current record set of the edited sheet 77 * @return string HTML 78 */ 79 80 function css_list($current) 81 { 82 $out = array(); 83 $safe_skin = doSlash($current['skin']); 84 $protected = safe_column("DISTINCT css", 'txp_section', "skin = '$safe_skin' OR dev_skin = '$safe_skin'"); 85 86 $criteria = "skin = '$safe_skin'"; 87 $criteria .= callback_event('admin_criteria', 'css_list', 0, $criteria); 88 89 $rs = safe_rows_start("name", 'txp_css', $criteria . ' ORDER BY name'); 90 91 while ($a = nextRow($rs)) { 92 extract($a); 93 94 $active = ($current['name'] === $name); 95 $edit = eLink('css', '', 'name', $name, $name); 96 97 if (!array_key_exists($name, $protected)) { 98 $edit .= dLink('css', 'css_delete', 'name', $name); 99 } 100 101 $out[] = tag(n.$edit.n, 'li', array('class' => $active ? 'active' : '')); 102 } 103 104 $list = wrapGroup('all_styles_css', tag(join(n, $out), 'ul', array('class' => 'switcher-list')), gTxt('all_stylesheets')); 105 106 return n.tag($list, 'div', array( 107 'id' => 'all_styles', 108 'role' => 'region', 109 ) 110 ); 111 } 112 113 /** 114 * The main stylesheet editor panel. 115 * 116 * @param string|array $message The activity message 117 * @param bool $refresh_partials Whether to refresh partial contents 118 */ 119 120 function css_edit($message = '', $refresh_partials = false) 121 { 122 global $instance, $event, $step; 123 124 /* 125 $partials is an array of: 126 $key => array ( 127 'mode' => {PARTIAL_STATIC | PARTIAL_VOLATILE | PARTIAL_VOLATILE_VALUE}, 128 'selector' => $DOM_selector or array($selector, $fragment) of $DOM_selectors, 129 'cb' => $callback_function, 130 'html' => $return_value_of_callback_function (need not be initialised here) 131 ) 132 */ 133 $partials = array( 134 // Stylesheet list. 135 'list' => array( 136 'mode' => PARTIAL_VOLATILE, 137 'selector' => '#all_styles', 138 'cb' => 'css_list', 139 ), 140 // Name field. 141 'name' => array( 142 'mode' => PARTIAL_VOLATILE, 143 'selector' => 'div.name', 144 'cb' => 'css_partial_name', 145 ), 146 // Name value. 147 'name_value' => array( 148 'mode' => PARTIAL_VOLATILE_VALUE, 149 'selector' => '#new_style,#main_content input[name=name]', 150 'cb' => 'css_partial_name_value', 151 ), 152 // Textarea. 153 'css' => array( 154 'mode' => PARTIAL_STATIC, 155 'selector' => 'div.css', 156 'cb' => 'css_partial_css', 157 ), 158 ); 159 160 extract(array_map('assert_string', gpsa(array( 161 'copy', 162 'save_error', 163 'savenew', 164 'skin', 165 )))); 166 167 $default_name = safe_field("css", 'txp_section', "name = 'default'"); 168 169 $name = assert_string(gps('name')); 170 $newname = Css::sanitize(assert_string(gps('newname'))); 171 $skin = ($skin !== '') ? $skin : null; 172 $class = 'async'; 173 174 $thisSkin = Txp::get('Textpattern\Skin\Skin'); 175 $skin = $thisSkin->setName($skin)->setEditing(); 176 177 if ($step == 'css_delete' || empty($name) && $step != 'pour' && !$savenew) { 178 $name = get_pref('last_css_saved', $default_name); 179 } elseif ((($copy || $savenew) && $newname) && !$save_error) { 180 $name = $newname; 181 } elseif ((($newname && ($newname != $name)) || $step === 'pour') && !$save_error) { 182 $name = $newname; 183 $class = ''; 184 } elseif ($savenew && $save_error) { 185 $class = ''; 186 } 187 188 if (!$save_error) { 189 $thecss = safe_field('css', 'txp_css', "name='".doSlash($name)."' AND skin='" . doSlash($skin) . "'"); 190 } else { 191 $thecss = gps('css'); 192 } 193 194 $actionsExtras = ''; 195 196 if ($name) { 197 $actionsExtras .= sLink('css', 'pour', '<span class="ui-icon ui-extra-icon-new-document"></span> '.gTxt('create_css'), 'txp-new') 198 .href('<span class="ui-icon ui-icon-copy"></span> '.gTxt('duplicate'), '#', array( 199 'class' => 'txp-clone', 200 'data-form' => 'style_form', 201 )); 202 } 203 204 $actions = graf( 205 $actionsExtras, 206 array('class' => 'txp-actions txp-actions-inline') 207 ); 208 209 $skinBlock = n.$instance->setSkin($thisSkin)->getSelectEdit(); 210 211 $buttons = graf( 212 (!is_writable($instance->getDirPath()) ? '' : 213 span( 214 checkbox2('export', gps('export'), 0, 'export'). 215 n.tag(gTxt('export_to_disk'), 'label', array('for' => 'export')) 216 , array('class' => 'txp-save-export')) 217 ).n. 218 tag_void('input', array( 219 'class' => 'publish', 220 'type' => 'submit', 221 'method' => 'post', 222 'value' => gTxt('save'), 223 )), ' class="txp-save"' 224 ); 225 226 $rs = array( 227 'name' => $name, 228 'newname' => $newname, 229 'default' => $default_name, 230 'skin' => $skin, 231 'css' => $thecss, 232 ); 233 234 // Get content for volatile partials. 235 $partials = updatePartials($partials, $rs, array(PARTIAL_VOLATILE, PARTIAL_VOLATILE_VALUE)); 236 237 if ($refresh_partials) { 238 $response[] = announce($message); 239 $response = array_merge($response, updateVolatilePartials($partials)); 240 send_script_response(join(";\n", $response)); 241 242 // Bail out. 243 return; 244 } 245 246 // Get content for static partials. 247 $partials = updatePartials($partials, $rs, PARTIAL_STATIC); 248 249 pagetop(gTxt('tab_style'), $message); 250 251 echo n.'<div class="txp-layout">'. 252 n.tag( 253 hed(gTxt('tab_style'), 1, array('class' => 'txp-heading')), 254 'div', array('class' => 'txp-layout-1col') 255 ); 256 257 // Styles create/switcher column. 258 echo n.tag( 259 $skinBlock.$partials['list']['html'].n, 260 'div', array( 261 'class' => 'txp-layout-4col-alt', 262 'id' => 'content_switcher', 263 'role' => 'region', 264 ) 265 ); 266 267 // Styles code column. 268 echo n.tag( 269 form( 270 $actions. 271 $partials['name']['html']. 272 $partials['css']['html']. 273 $buttons, '', '', 'post', $class, '', 'style_form'), 274 'div', array( 275 'class' => 'txp-layout-4col-3span', 276 'id' => 'main_content', 277 'role' => 'region', 278 ) 279 ); 280 281 echo n.'</div>'; // End of .txp-layout. 282 } 283 284 /** 285 * Saves or clones a stylesheet. 286 */ 287 288 function css_save() 289 { 290 global $instance, $app_mode; 291 292 extract(doSlash(array_map('assert_string', psa(array( 293 'savenew', 294 'copy', 295 'css', 296 'skin', 297 ))))); 298 299 $passedName = assert_string(ps('name')); 300 $name = Css::sanitize($passedName); 301 $newname = Css::sanitize(assert_string(ps('newname'))); 302 303 $skin = Txp::get('Textpattern\Skin\Skin')->setName($skin)->setEditing(); 304 305 $save_error = false; 306 $message = ''; 307 308 if (!$newname) { 309 $message = array(gTxt('css_name_required'), E_ERROR); 310 $save_error = true; 311 } else { 312 if ($copy && ($name === $newname)) { 313 $newname .= '_copy'; 314 $passedName = $name; 315 $_POST['newname'] = $newname; 316 } 317 318 $safe_skin = doSlash($skin); 319 $safe_name = doSlash($passedName); 320 $safe_newname = doSlash($newname); 321 322 $exists = safe_field('name', 'txp_css', "name = '$safe_newname' AND skin = '$safe_skin'"); 323 324 if (($newname !== $name) && $exists) { 325 $message = array(gTxt('css_already_exists', array('{name}' => $newname)), E_ERROR); 326 327 if ($savenew) { 328 $_POST['newname'] = ''; 329 } 330 331 $save_error = true; 332 } else { 333 if ($savenew or $copy) { 334 if ($newname) { 335 if (safe_insert('txp_css', "name = '$safe_newname', css = '$css', skin = '$safe_skin'")) { 336 set_pref('last_css_saved', $newname, 'css', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); 337 update_lastmod('css_created', compact('newname', 'name', 'css')); 338 339 $message = gTxt('css_created', array('{list}' => $newname)); 340 341 // If css name has been auto-sanitized, throw a warning. 342 if ($passedName !== $name) { 343 $message = array($message, E_WARNING); 344 } 345 346 callback_event($copy ? 'css_duplicated' : 'css_created', '', 0, $name, $newname); 347 } else { 348 $message = array(gTxt('css_save_failed'), E_ERROR); 349 $save_error = true; 350 } 351 } else { 352 $message = array(gTxt('css_name_required'), E_ERROR); 353 $save_error = true; 354 } 355 } else { 356 if (safe_update('txp_css', 357 "css = '$css', name = '$safe_newname', skin = '$safe_skin'", 358 "name = '$safe_name' AND skin = '$safe_skin'")) { 359 safe_update('txp_section', "css = '$safe_newname'", "css='$safe_name' AND skin='$safe_skin'"); 360 safe_update('txp_section', "dev_css = '$safe_newname'", "dev_css='$safe_name' AND dev_skin='$safe_skin'"); 361 set_pref('last_css_saved', $newname, 'css', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); 362 update_lastmod('css_saved', compact('newname', 'name', 'css')); 363 364 $message = gTxt('css_updated', array('{list}' => $newname)); 365 366 // If css name has been auto-sanitized, throw a warning. 367 if ($passedName !== $name) { 368 $message = array($message, E_WARNING); 369 } 370 371 callback_event('css_updated', '', 0, $name, $newname); 372 } else { 373 $message = array(gTxt('css_save_failed'), E_ERROR); 374 $save_error = true; 375 } 376 } 377 } 378 } 379 380 if ($save_error === true) { 381 $_POST['save_error'] = '1'; 382 } else { 383 if (gps('export')) { 384 $instance->setNames(array($newname))->export()->getMessage(); 385 } 386 387 callback_event('css_saved', '', 0, $name, $newname); 388 } 389 390 css_edit($message, ($app_mode === 'async') ? true : false); 391 } 392 393 /** 394 * Deletes a stylesheet. 395 */ 396 397 function css_delete() 398 { 399 global $prefs; 400 401 $name = ps('name'); 402 $safe_name = doSlash($name); 403 $skin = get_pref('skin_editing', 'default'); 404 $safe_skin = doSlash($skin); 405 406 $count = safe_count('txp_section', "css = '$safe_name' AND (skin='$safe_skin' OR dev_skin='$safe_skin')"); 407 $message = ''; 408 409 if ($count) { 410 $message = array(gTxt('css_used_by_section', array('{name}' => $name, '{count}' => $count)), E_ERROR); 411 } else { 412 if (safe_delete('txp_css', "name = '$safe_name' AND skin='$safe_skin'")) { 413 callback_event('css_deleted', '', 0, compact('name', 'skin')); 414 $message = gTxt('css_deleted', array('{list}' => $name)); 415 if ($name === get_pref('last_css_saved')) { 416 unset($prefs['last_css_saved']); 417 remove_pref('last_css_saved', 'css'); 418 } 419 } 420 } 421 422 css_edit($message); 423 } 424 425 /** 426 * Changes the skin in which styles are being edited. 427 * 428 * Keeps track of which skin is being edited from panel to panel. 429 * 430 * @param string $skin Optional skin name. Read from GET/POST otherwise 431 * @deprecated in 4.7.0 432 */ 433 434 function css_skin_change($skin = null) 435 { 436 Txp::get('Textpattern\Skin\Css')->selectEdit($skin); 437 438 return true; 439 } 440 441 /** 442 * Renders css name field. 443 * 444 * @param array $rs Record set 445 * @return string HTML 446 */ 447 448 function css_partial_name($rs) 449 { 450 $name = $rs['name']; 451 $skin = $rs['skin']; 452 $nameRegex = '^(?=[^.\s])[^\x00-\x1f\x22\x26\x27\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+'; 453 454 $titleblock = inputLabel( 455 'new_style', 456 fInput('text', array('name' => 'newname', 'pattern' => $nameRegex), $name, 'input-medium', '', '', INPUT_MEDIUM, '', 'new_style', false, true), 457 'css_name', 458 array('', 'instructions_style_name'), 459 array('class' => 'txp-form-field name') 460 ); 461 462 if ($name === '') { 463 $titleblock .= hInput('savenew', 'savenew'); 464 } else { 465 $titleblock .= hInput('name', $name); 466 } 467 468 $titleblock .= hInput('skin', $skin). 469 eInput('css').sInput('css_save'); 470 471 return $titleblock; 472 } 473 474 /** 475 * Renders css name value. 476 * 477 * @param array $rs Record set 478 * @return string HTML 479 */ 480 481 function css_partial_name_value($rs) 482 { 483 return $rs['name']; 484 } 485 486 /** 487 * Renders css textarea field. 488 * 489 * @param array $rs Record set 490 * @return string HTML 491 */ 492 493 function css_partial_css($rs) 494 { 495 $out = inputLabel( 496 'css', 497 '<textarea class="code" id="css" name="css" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr">'.txpspecialchars($rs['css']).'</textarea>', 498 'css_code', 499 array('', 'instructions_style_code'), 500 array('class' => 'txp-form-field css') 501 ); 502 503 return $out; 504 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title