Textpattern | PHP Cross Reference | Content Management Systems |
Description: Pages 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 * Pages panel. 26 * 27 * @package Admin\Page 28 */ 29 30 use Textpattern\Skin\Skin; 31 use Textpattern\Skin\Page; 32 33 if (!defined('txpinterface')) { 34 die('txpinterface is undefined.'); 35 } 36 37 if ($event == 'page') { 38 require_privs('page'); 39 40 $instance = Txp::get('Textpattern\Skin\Page'); 41 42 bouncer($step, array( 43 'page_edit' => false, 44 'page_save' => true, 45 'page_delete' => true, 46 'page_skin_change' => true, 47 'tagbuild' => false, 48 )); 49 50 switch (strtolower($step)) { 51 case '': 52 page_edit(); 53 break; 54 case 'page_edit': 55 page_edit(); 56 break; 57 case 'page_save': 58 page_save(); 59 break; 60 case 'page_delete': 61 page_delete(); 62 break; 63 case 'page_new': 64 page_new(); 65 break; 66 case "page_skin_change": 67 $instance->selectEdit(); 68 page_edit(); 69 break; 70 case 'tagbuild': 71 echo page_tagbuild(); 72 break; 73 } 74 } 75 76 /** 77 * The main Page editor panel. 78 * 79 * @param string|array $message The activity message 80 * @param bool $refresh_partials Whether to refresh partial contents 81 */ 82 83 function page_edit($message = '', $refresh_partials = false) 84 { 85 global $instance, $event, $step; 86 87 /* 88 $partials is an array of: 89 $key => array ( 90 'mode' => {PARTIAL_STATIC | PARTIAL_VOLATILE | PARTIAL_VOLATILE_VALUE}, 91 'selector' => $DOM_selector or array($selector, $fragment) of $DOM_selectors, 92 'cb' => $callback_function, 93 'html' => $return_value_of_callback_function (need not be initialised here) 94 ) 95 */ 96 $partials = array( 97 // Stylesheet list. 98 'list' => array( 99 'mode' => PARTIAL_VOLATILE, 100 'selector' => '#all_pages', 101 'cb' => 'page_list', 102 ), 103 // Name field. 104 'name' => array( 105 'mode' => PARTIAL_VOLATILE, 106 'selector' => 'div.name', 107 'cb' => 'page_partial_name', 108 ), 109 // Name value. 110 'name_value' => array( 111 'mode' => PARTIAL_VOLATILE_VALUE, 112 'selector' => '#new_page,#main_content input[name=name]', 113 'cb' => 'page_partial_name_value', 114 ), 115 // Textarea. 116 'template' => array( 117 'mode' => PARTIAL_STATIC, 118 'selector' => 'div.template', 119 'cb' => 'page_partial_template', 120 ), 121 ); 122 123 extract(array_map('assert_string', gpsa(array( 124 'copy', 125 'save_error', 126 'savenew', 127 'skin', 128 )))); 129 130 $default_name = safe_field("page", 'txp_section', "name = 'default'"); 131 132 $name = assert_string(gps('name')); 133 $newname = Page::sanitize(assert_string(gps('newname'))); 134 $skin = ($skin !== '') ? $skin : null; 135 $class = 'async'; 136 137 $thisSkin = Txp::get('Textpattern\Skin\Skin'); 138 $skin = $thisSkin->setName($skin)->setEditing(); 139 140 if ($step == 'page_delete' || empty($name) && $step != 'page_new' && !$savenew) { 141 $name = get_pref('last_page_saved', $default_name); 142 } elseif ((($copy || $savenew) && $newname) && !$save_error) { 143 $name = $newname; 144 } elseif ((($newname && ($newname != $name)) || $step === 'page_new') && !$save_error) { 145 $name = $newname; 146 $class = ''; 147 } elseif ($savenew && $save_error) { 148 $class = ''; 149 } 150 151 if (!$save_error) { 152 $html = safe_field('user_html', 'txp_page', "name = '".doSlash($name)."' AND skin = '" . doSlash($skin) . "'"); 153 } else { 154 $html = gps('html'); 155 } 156 157 $actionsExtras = ''; 158 159 if ($name) { 160 $actionsExtras .= sLink('page', 'page_new', '<span class="ui-icon ui-extra-icon-new-document"></span> '.gTxt('create_page'), 'txp-new') 161 .href('<span class="ui-icon ui-icon-copy"></span> '.gTxt('duplicate'), '#', 162 array( 163 'class' => 'txp-clone', 164 'data-form' => 'page_form', 165 ) 166 ); 167 } 168 169 $actions = graf( 170 $actionsExtras, 171 array('class' => 'txp-actions txp-actions-inline') 172 ); 173 174 $skinBlock = n.$instance->setSkin($thisSkin)->getSelectEdit(); 175 176 $buttons = graf( 177 (!is_writable($instance->getDirPath()) ? '' : 178 span( 179 checkbox2('export', gps('export'), 0, 'export'). 180 n.tag(gTxt('export_to_disk'), 'label', array('for' => 'export')) 181 , array('class' => 'txp-save-export')) 182 ).n. 183 tag_void('input', array( 184 'class' => 'publish', 185 'type' => 'submit', 186 'method' => 'post', 187 'value' => gTxt('save'), 188 )), ' class="txp-save"' 189 ); 190 191 $rs = array( 192 'name' => $name, 193 'newname' => $newname, 194 'default' => $default_name, 195 'skin' => $skin, 196 'html' => $html, 197 ); 198 199 // Get content for volatile partials. 200 $partials = updatePartials($partials, $rs, array(PARTIAL_VOLATILE, PARTIAL_VOLATILE_VALUE)); 201 202 if ($refresh_partials) { 203 $response[] = announce($message); 204 $response = array_merge($response, updateVolatilePartials($partials)); 205 send_script_response(join(";\n", $response)); 206 207 // Bail out. 208 return; 209 } 210 211 // Get content for static partials. 212 $partials = updatePartials($partials, $rs, PARTIAL_STATIC); 213 214 pagetop(gTxt('tab_pages'), $message); 215 216 echo n.'<div class="txp-layout">'. 217 n.tag( 218 hed(gTxt('tab_pages'), 1, array('class' => 'txp-heading')), 219 'div', array('class' => 'txp-layout-1col') 220 ); 221 222 // Pages create/switcher column. 223 echo n.tag( 224 $skinBlock.$partials['list']['html'].n, 225 'div', array( 226 'class' => 'txp-layout-4col-alt', 227 'id' => 'content_switcher', 228 'role' => 'region', 229 ) 230 ); 231 232 // Pages code column. 233 echo n.tag( 234 form( 235 $actions. 236 $partials['name']['html']. 237 $partials['template']['html']. 238 $buttons, '', '', 'post', $class, '', 'page_form'), 239 'div', array( 240 'class' => 'txp-layout-4col-3span', 241 'id' => 'main_content', 242 'role' => 'region', 243 ) 244 ); 245 246 // Tag builder dialog placeholder. 247 echo n.tag( 248 ' ', 249 'div', array( 250 'class' => 'txp-tagbuilder-content', 251 'id' => 'tagbuild_links', 252 'aria-label' => gTxt('tagbuilder'), 253 'title' => gTxt('tagbuilder'), 254 )); 255 256 echo n.'</div>'; // End of .txp-layout. 257 } 258 259 /** 260 * Renders a list of page templates. 261 * 262 * @param string $current The selected template info 263 * @return string HTML 264 */ 265 266 function page_list($current) 267 { 268 $out = array(); 269 $safe_skin = doSlash($current['skin']); 270 $protected = safe_column("DISTINCT page", 'txp_section', "skin = '$safe_skin' OR dev_skin = '$safe_skin'") + array('error_default'); 271 272 $criteria = "skin = '$safe_skin'"; 273 $criteria .= callback_event('admin_criteria', 'page_list', 0, $criteria); 274 275 $rs = safe_rows_start("name", 'txp_page', "$criteria ORDER BY name ASC"); 276 277 if ($rs) { 278 while ($a = nextRow($rs)) { 279 extract($a); 280 $active = ($current['name'] === $name); 281 282 $edit = eLink('page', '', 'name', $name, $name); 283 284 if (!in_array($name, $protected)) { 285 $edit .= dLink('page', 'page_delete', 'name', $name); 286 } 287 288 $out[] = tag(n.$edit.n, 'li', array('class' => $active ? 'active' : '')); 289 } 290 291 $out = tag(join(n, $out), 'ul', array('class' => 'switcher-list')); 292 293 return wrapGroup('all_pages', $out, 'all_pages'); 294 } 295 } 296 297 /** 298 * Deletes a page template. 299 */ 300 301 function page_delete() 302 { 303 global $prefs; 304 305 $name = ps('name'); 306 $safe_name = doSlash($name); 307 $skin = get_pref('skin_editing', 'default'); 308 $safe_skin = doSlash($skin); 309 $count = safe_count('txp_section', "page = '$safe_name' AND (skin='$safe_skin' OR dev_skin='$safe_skin')"); 310 $message = ''; 311 312 if ($name == 'error_default') { 313 return page_edit(); 314 } 315 316 if ($count) { 317 $message = array(gTxt('page_used_by_section', array( 318 '{name}' => $name, 319 '{count}' => $count, 320 )), E_WARNING); 321 } else { 322 if (safe_delete('txp_page', "name = '$safe_name' AND skin='$safe_skin'")) { 323 callback_event('page_deleted', '', 0, compact('name', 'skin')); 324 $message = gTxt('page_deleted', array('{list}' => $name)); 325 if ($name === get_pref('last_page_saved')) { 326 unset($prefs['last_page_saved']); 327 remove_pref('last_page_saved', 'page'); 328 } 329 } 330 } 331 332 page_edit($message); 333 } 334 335 /** 336 * Changes the skin in which styles are being edited. 337 * 338 * Keeps track of which skin is being edited from panel to panel. 339 * 340 * @param string $skin Optional skin name. Read from GET/POST otherwise 341 * @deprecated in 4.7.0 342 */ 343 344 function page_skin_change($skin = null) 345 { 346 Txp::get('Textpattern\Skin\Page')->selectEdit($skin); 347 348 return true; 349 } 350 351 /** 352 * Saves or clones a page template. 353 */ 354 355 function page_save() 356 { 357 global $instance, $app_mode; 358 359 extract(doSlash(array_map('assert_string', psa(array( 360 'savenew', 361 'html', 362 'copy', 363 'skin', 364 ))))); 365 366 $passedName = assert_string(ps('name')); 367 $name = Page::sanitize($passedName); 368 $newname = Page::sanitize(assert_string(ps('newname'))); 369 370 $skin = Txp::get('Textpattern\Skin\Skin')->setName($skin)->setEditing(); 371 372 $save_error = false; 373 $message = ''; 374 375 if (!$newname) { 376 $message = array(gTxt('page_name_invalid'), E_ERROR); 377 $save_error = true; 378 } else { 379 if ($copy && ($name === $newname)) { 380 $newname .= '_copy'; 381 $passedName = $name; 382 $_POST['newname'] = $newname; 383 } 384 385 $safe_skin = doSlash($skin); 386 $safe_name = doSlash($passedName); 387 $safe_newname = doSlash($newname); 388 389 $exists = safe_field('name', 'txp_page', "name = '$safe_newname' AND skin = '$safe_skin'"); 390 391 if ($newname !== $name && $exists !== false) { 392 $message = array(gTxt('page_already_exists', array('{name}' => $newname)), E_ERROR); 393 394 if ($savenew) { 395 $_POST['newname'] = ''; 396 } 397 398 $save_error = true; 399 } else { 400 if ($savenew or $copy) { 401 if ($newname) { 402 if (safe_insert('txp_page', "name = '$safe_newname', user_html = '$html', skin = '$safe_skin'")) { 403 set_pref('last_page_saved', $newname, 'page', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); 404 update_lastmod('page_created', compact('newname', 'name', 'html')); 405 406 $message = gTxt('page_created', array('{list}' => $newname)); 407 408 // If page name has been auto-sanitized, throw a warning. 409 if ($passedName !== $name) { 410 $message = array($message, E_WARNING); 411 } 412 413 callback_event($copy ? 'page_duplicated' : 'page_created', '', 0, $name, $newname); 414 } else { 415 $message = array(gTxt('page_save_failed'), E_ERROR); 416 $save_error = true; 417 } 418 } else { 419 $message = array(gTxt('page_name_invalid'), E_ERROR); 420 $save_error = true; 421 } 422 } else { 423 if (safe_update('txp_page', 424 "user_html = '$html', name = '$safe_newname', skin = '$safe_skin'", 425 "name = '$safe_name' AND skin = '$safe_skin'")) { 426 safe_update('txp_section', "page = '$safe_newname'", "page='$safe_name' AND skin='$safe_skin'"); 427 safe_update('txp_section', "dev_page = '$safe_newname'", "dev_page='$safe_name' AND dev_skin='$safe_skin'"); 428 set_pref('last_page_saved', $newname, 'page', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); 429 update_lastmod('page_saved', compact('newname', 'name', 'html')); 430 431 $message = gTxt('page_updated', array('{list}' => $newname)); 432 433 // If page name has been auto-sanitized, throw a warning. 434 if ($passedName !== $name) { 435 $message = array($message, E_WARNING); 436 } 437 438 callback_event('page_updated', '', 0, $name, $newname); 439 } else { 440 $message = array(gTxt('page_save_failed'), E_ERROR); 441 $save_error = true; 442 } 443 } 444 } 445 } 446 447 if ($save_error === true) { 448 $_POST['save_error'] = '1'; 449 } else { 450 if (gps('export')) { 451 $instance->setNames(array($newname))->export()->getMessage(); 452 } 453 454 callback_event('page_saved', '', 0, $name, $newname); 455 } 456 457 page_edit($message, ($app_mode === 'async') ? true : false); 458 } 459 460 /** 461 * Directs requests to page_edit() armed with a 'page_new' step. 462 * 463 * @see page_edit() 464 */ 465 466 function page_new() 467 { 468 page_edit(); 469 } 470 471 /** 472 * Return a list of tag builder tags. 473 * 474 * @return HTML 475 */ 476 477 function page_tagbuild() 478 { 479 $listActions = graf( 480 href('<span class="ui-icon ui-icon-arrowthickstop-1-s"></span> '.gTxt('expand_all'), '#', array( 481 'class' => 'txp-expand-all', 482 'aria-controls' => 'tagbuild_links', 483 )). 484 href('<span class="ui-icon ui-icon-arrowthickstop-1-n"></span> '.gTxt('collapse_all'), '#', array( 485 'class' => 'txp-collapse-all', 486 'aria-controls' => 'tagbuild_links', 487 )), array('class' => 'txp-actions') 488 ); 489 490 // Format of each entry is popTagLink -> array ( gTxt() string, class/ID). 491 $tagbuild_items = array( 492 'page_article' => array('page_article_hed', 'article-tags'), 493 'page_article_nav' => array('page_article_nav_hed', 'article-nav-tags'), 494 'page_nav' => array('page_nav_hed', 'nav-tags'), 495 'page_xml' => array('page_xml_hed', 'xml-tags'), 496 'page_misc' => array('page_misc_hed', 'misc-tags'), 497 'page_file' => array('page_file_hed', 'file-tags'), 498 ); 499 500 $tagbuild_links = ''; 501 502 foreach ($tagbuild_items as $tb => $item) { 503 $tagbuild_links .= wrapRegion($item[1].'_group', taglinks($tb), $item[1], $item[0], 'page_'.$item[1]); 504 } 505 506 return $listActions.$tagbuild_links; 507 } 508 509 /** 510 * Renders a list of tag builder options. 511 * 512 * @param string $type 513 * @return HTML 514 * @access private 515 * @see popTagLinks() 516 */ 517 518 function taglinks($type) 519 { 520 return popTagLinks($type); 521 } 522 523 /** 524 * Renders page name field. 525 * 526 * @param array $rs Record set 527 * @return string HTML 528 */ 529 530 function page_partial_name($rs) 531 { 532 $name = $rs['name']; 533 $skin = $rs['skin']; 534 $nameRegex = '^(?=[^.\s])[^\x00-\x1f\x22\x26\x27\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+'; 535 536 $titleblock = inputLabel( 537 'new_page', 538 fInput('text', array('name' => 'newname', 'pattern' => $nameRegex), $name, 'input-medium', '', '', INPUT_MEDIUM, '', 'new_page', false, true), 539 'page_name', 540 array('', 'instructions_page_name'), 541 array('class' => 'txp-form-field name') 542 ); 543 544 if ($name === '') { 545 $titleblock .= hInput('savenew', 'savenew'); 546 } else { 547 $titleblock .= hInput('name', $name); 548 } 549 550 $titleblock .= hInput('skin', $skin). 551 eInput('page').sInput('page_save'); 552 553 return $titleblock; 554 } 555 556 /** 557 * Renders page name value. 558 * 559 * @param array $rs Record set 560 * @return string HTML 561 */ 562 563 function page_partial_name_value($rs) 564 { 565 return $rs['name']; 566 } 567 568 /** 569 * Renders page textarea field. 570 * 571 * @param array $rs Record set 572 * @return string HTML 573 */ 574 575 function page_partial_template($rs) 576 { 577 global $event; 578 579 $out = inputLabel( 580 'html', 581 '<textarea class="code" id="html" name="html" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr">'.txpspecialchars($rs['html']).'</textarea>', 582 array( 583 'page_code', 584 n.span( 585 (has_privs('tag') 586 ? href( 587 span(null, array('class' => 'ui-icon ui-extra-icon-code')).' '.gTxt('tagbuilder'), 588 array('event' => 'tag', 'panel' => $event), 589 array('class' => 'txp-tagbuilder-dialog') 590 ) 591 : '' 592 ), 593 array('class' => 'txp-textarea-options') 594 ) 595 ), 596 array('', 'instructions_page_code'), 597 array('class' => 'txp-form-field template'), 598 array('div', 'div') 599 ); 600 601 return $out; 602 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title