. */ /** * Pages panel. * * @package Admin\Page */ use Textpattern\Skin\Skin; use Textpattern\Skin\Page; if (!defined('txpinterface')) { die('txpinterface is undefined.'); } if ($event == 'page') { require_privs('page'); $instance = Txp::get('Textpattern\Skin\Page'); bouncer($step, array( 'page_edit' => false, 'page_save' => true, 'page_delete' => true, 'page_skin_change' => true, 'tagbuild' => false, )); switch (strtolower($step)) { case '': page_edit(); break; case 'page_edit': page_edit(); break; case 'page_save': page_save(); break; case 'page_delete': page_delete(); break; case 'page_new': page_new(); break; case "page_skin_change": $instance->selectEdit(); page_edit(); break; case 'tagbuild': echo page_tagbuild(); break; } } /** * The main Page editor panel. * * @param string|array $message The activity message * @param bool $refresh_partials Whether to refresh partial contents */ function page_edit($message = '', $refresh_partials = false) { global $instance, $event, $step; /* $partials is an array of: $key => array ( 'mode' => {PARTIAL_STATIC | PARTIAL_VOLATILE | PARTIAL_VOLATILE_VALUE}, 'selector' => $DOM_selector or array($selector, $fragment) of $DOM_selectors, 'cb' => $callback_function, 'html' => $return_value_of_callback_function (need not be initialised here) ) */ $partials = array( // Stylesheet list. 'list' => array( 'mode' => PARTIAL_VOLATILE, 'selector' => '#all_pages', 'cb' => 'page_list', ), // Name field. 'name' => array( 'mode' => PARTIAL_VOLATILE, 'selector' => 'div.name', 'cb' => 'page_partial_name', ), // Name value. 'name_value' => array( 'mode' => PARTIAL_VOLATILE_VALUE, 'selector' => '#new_page,#main_content input[name=name]', 'cb' => 'page_partial_name_value', ), // Textarea. 'template' => array( 'mode' => PARTIAL_STATIC, 'selector' => 'div.template', 'cb' => 'page_partial_template', ), ); extract(array_map('assert_string', gpsa(array( 'copy', 'save_error', 'savenew', 'skin', )))); $default_name = safe_field("page", 'txp_section', "name = 'default'"); $name = assert_string(gps('name')); $newname = Page::sanitize(assert_string(gps('newname'))); $skin = ($skin !== '') ? $skin : null; $class = 'async'; $thisSkin = Txp::get('Textpattern\Skin\Skin'); $skin = $thisSkin->setName($skin)->setEditing(); if ($step == 'page_delete' || empty($name) && $step != 'page_new' && !$savenew) { $name = get_pref('last_page_saved', $default_name); } elseif ((($copy || $savenew) && $newname) && !$save_error) { $name = $newname; } elseif ((($newname && ($newname != $name)) || $step === 'page_new') && !$save_error) { $name = $newname; $class = ''; } elseif ($savenew && $save_error) { $class = ''; } if (!$save_error) { $html = safe_field('user_html', 'txp_page', "name = '".doSlash($name)."' AND skin = '" . doSlash($skin) . "'"); } else { $html = gps('html'); } $actionsExtras = ''; if ($name) { $actionsExtras .= sLink('page', 'page_new', ' '.gTxt('create_page'), 'txp-new') .href(' '.gTxt('duplicate'), '#', array( 'class' => 'txp-clone', 'data-form' => 'page_form', ) ); } $actions = graf( $actionsExtras, array('class' => 'txp-actions txp-actions-inline') ); $skinBlock = n.$instance->setSkin($thisSkin)->getSelectEdit(); $buttons = graf( (!is_writable($instance->getDirPath()) ? '' : span( checkbox2('export', gps('export'), 0, 'export'). n.tag(gTxt('export_to_disk'), 'label', array('for' => 'export')) , array('class' => 'txp-save-export')) ).n. tag_void('input', array( 'class' => 'publish', 'type' => 'submit', 'method' => 'post', 'value' => gTxt('save'), )), ' class="txp-save"' ); $rs = array( 'name' => $name, 'newname' => $newname, 'default' => $default_name, 'skin' => $skin, 'html' => $html, ); // Get content for volatile partials. $partials = updatePartials($partials, $rs, array(PARTIAL_VOLATILE, PARTIAL_VOLATILE_VALUE)); if ($refresh_partials) { $response[] = announce($message); $response = array_merge($response, updateVolatilePartials($partials)); send_script_response(join(";\n", $response)); // Bail out. return; } // Get content for static partials. $partials = updatePartials($partials, $rs, PARTIAL_STATIC); pagetop(gTxt('tab_pages'), $message); echo n.'
'. n.tag( hed(gTxt('tab_pages'), 1, array('class' => 'txp-heading')), 'div', array('class' => 'txp-layout-1col') ); // Pages create/switcher column. echo n.tag( $skinBlock.$partials['list']['html'].n, 'div', array( 'class' => 'txp-layout-4col-alt', 'id' => 'content_switcher', 'role' => 'region', ) ); // Pages code column. echo n.tag( form( $actions. $partials['name']['html']. $partials['template']['html']. $buttons, '', '', 'post', $class, '', 'page_form'), 'div', array( 'class' => 'txp-layout-4col-3span', 'id' => 'main_content', 'role' => 'region', ) ); // Tag builder dialog placeholder. echo n.tag( ' ', 'div', array( 'class' => 'txp-tagbuilder-content', 'id' => 'tagbuild_links', 'aria-label' => gTxt('tagbuilder'), 'title' => gTxt('tagbuilder'), )); echo n.'
'; // End of .txp-layout. } /** * Renders a list of page templates. * * @param string $current The selected template info * @return string HTML */ function page_list($current) { $out = array(); $safe_skin = doSlash($current['skin']); $protected = safe_column("DISTINCT page", 'txp_section', "skin = '$safe_skin' OR dev_skin = '$safe_skin'") + array('error_default'); $criteria = "skin = '$safe_skin'"; $criteria .= callback_event('admin_criteria', 'page_list', 0, $criteria); $rs = safe_rows_start("name", 'txp_page', "$criteria ORDER BY name ASC"); if ($rs) { while ($a = nextRow($rs)) { extract($a); $active = ($current['name'] === $name); $edit = eLink('page', '', 'name', $name, $name); if (!in_array($name, $protected)) { $edit .= dLink('page', 'page_delete', 'name', $name); } $out[] = tag(n.$edit.n, 'li', array('class' => $active ? 'active' : '')); } $out = tag(join(n, $out), 'ul', array('class' => 'switcher-list')); return wrapGroup('all_pages', $out, 'all_pages'); } } /** * Deletes a page template. */ function page_delete() { global $prefs; $name = ps('name'); $safe_name = doSlash($name); $skin = get_pref('skin_editing', 'default'); $safe_skin = doSlash($skin); $count = safe_count('txp_section', "page = '$safe_name' AND (skin='$safe_skin' OR dev_skin='$safe_skin')"); $message = ''; if ($name == 'error_default') { return page_edit(); } if ($count) { $message = array(gTxt('page_used_by_section', array( '{name}' => $name, '{count}' => $count, )), E_WARNING); } else { if (safe_delete('txp_page', "name = '$safe_name' AND skin='$safe_skin'")) { callback_event('page_deleted', '', 0, compact('name', 'skin')); $message = gTxt('page_deleted', array('{list}' => $name)); if ($name === get_pref('last_page_saved')) { unset($prefs['last_page_saved']); remove_pref('last_page_saved', 'page'); } } } page_edit($message); } /** * Changes the skin in which styles are being edited. * * Keeps track of which skin is being edited from panel to panel. * * @param string $skin Optional skin name. Read from GET/POST otherwise * @deprecated in 4.7.0 */ function page_skin_change($skin = null) { Txp::get('Textpattern\Skin\Page')->selectEdit($skin); return true; } /** * Saves or clones a page template. */ function page_save() { global $instance, $app_mode; extract(doSlash(array_map('assert_string', psa(array( 'savenew', 'html', 'copy', 'skin', ))))); $passedName = assert_string(ps('name')); $name = Page::sanitize($passedName); $newname = Page::sanitize(assert_string(ps('newname'))); $skin = Txp::get('Textpattern\Skin\Skin')->setName($skin)->setEditing(); $save_error = false; $message = ''; if (!$newname) { $message = array(gTxt('page_name_invalid'), E_ERROR); $save_error = true; } else { if ($copy && ($name === $newname)) { $newname .= '_copy'; $passedName = $name; $_POST['newname'] = $newname; } $safe_skin = doSlash($skin); $safe_name = doSlash($passedName); $safe_newname = doSlash($newname); $exists = safe_field('name', 'txp_page', "name = '$safe_newname' AND skin = '$safe_skin'"); if ($newname !== $name && $exists !== false) { $message = array(gTxt('page_already_exists', array('{name}' => $newname)), E_ERROR); if ($savenew) { $_POST['newname'] = ''; } $save_error = true; } else { if ($savenew or $copy) { if ($newname) { if (safe_insert('txp_page', "name = '$safe_newname', user_html = '$html', skin = '$safe_skin'")) { set_pref('last_page_saved', $newname, 'page', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); update_lastmod('page_created', compact('newname', 'name', 'html')); $message = gTxt('page_created', array('{list}' => $newname)); // If page name has been auto-sanitized, throw a warning. if ($passedName !== $name) { $message = array($message, E_WARNING); } callback_event($copy ? 'page_duplicated' : 'page_created', '', 0, $name, $newname); } else { $message = array(gTxt('page_save_failed'), E_ERROR); $save_error = true; } } else { $message = array(gTxt('page_name_invalid'), E_ERROR); $save_error = true; } } else { if (safe_update('txp_page', "user_html = '$html', name = '$safe_newname', skin = '$safe_skin'", "name = '$safe_name' AND skin = '$safe_skin'")) { safe_update('txp_section', "page = '$safe_newname'", "page='$safe_name' AND skin='$safe_skin'"); safe_update('txp_section', "dev_page = '$safe_newname'", "dev_page='$safe_name' AND dev_skin='$safe_skin'"); set_pref('last_page_saved', $newname, 'page', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); update_lastmod('page_saved', compact('newname', 'name', 'html')); $message = gTxt('page_updated', array('{list}' => $newname)); // If page name has been auto-sanitized, throw a warning. if ($passedName !== $name) { $message = array($message, E_WARNING); } callback_event('page_updated', '', 0, $name, $newname); } else { $message = array(gTxt('page_save_failed'), E_ERROR); $save_error = true; } } } } if ($save_error === true) { $_POST['save_error'] = '1'; } else { if (gps('export')) { $instance->setNames(array($newname))->export()->getMessage(); } callback_event('page_saved', '', 0, $name, $newname); } page_edit($message, ($app_mode === 'async') ? true : false); } /** * Directs requests to page_edit() armed with a 'page_new' step. * * @see page_edit() */ function page_new() { page_edit(); } /** * Return a list of tag builder tags. * * @return HTML */ function page_tagbuild() { $listActions = graf( href(' '.gTxt('expand_all'), '#', array( 'class' => 'txp-expand-all', 'aria-controls' => 'tagbuild_links', )). href(' '.gTxt('collapse_all'), '#', array( 'class' => 'txp-collapse-all', 'aria-controls' => 'tagbuild_links', )), array('class' => 'txp-actions') ); // Format of each entry is popTagLink -> array ( gTxt() string, class/ID). $tagbuild_items = array( 'page_article' => array('page_article_hed', 'article-tags'), 'page_article_nav' => array('page_article_nav_hed', 'article-nav-tags'), 'page_nav' => array('page_nav_hed', 'nav-tags'), 'page_xml' => array('page_xml_hed', 'xml-tags'), 'page_misc' => array('page_misc_hed', 'misc-tags'), 'page_file' => array('page_file_hed', 'file-tags'), ); $tagbuild_links = ''; foreach ($tagbuild_items as $tb => $item) { $tagbuild_links .= wrapRegion($item[1].'_group', taglinks($tb), $item[1], $item[0], 'page_'.$item[1]); } return $listActions.$tagbuild_links; } /** * Renders a list of tag builder options. * * @param string $type * @return HTML * @access private * @see popTagLinks() */ function taglinks($type) { return popTagLinks($type); } /** * Renders page name field. * * @param array $rs Record set * @return string HTML */ function page_partial_name($rs) { $name = $rs['name']; $skin = $rs['skin']; $nameRegex = '^(?=[^.\s])[^\x00-\x1f\x22\x26\x27\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+'; $titleblock = inputLabel( 'new_page', fInput('text', array('name' => 'newname', 'pattern' => $nameRegex), $name, 'input-medium', '', '', INPUT_MEDIUM, '', 'new_page', false, true), 'page_name', array('', 'instructions_page_name'), array('class' => 'txp-form-field name') ); if ($name === '') { $titleblock .= hInput('savenew', 'savenew'); } else { $titleblock .= hInput('name', $name); } $titleblock .= hInput('skin', $skin). eInput('page').sInput('page_save'); return $titleblock; } /** * Renders page name value. * * @param array $rs Record set * @return string HTML */ function page_partial_name_value($rs) { return $rs['name']; } /** * Renders page textarea field. * * @param array $rs Record set * @return string HTML */ function page_partial_template($rs) { global $event; $out = inputLabel( 'html', '', array( 'page_code', n.span( (has_privs('tag') ? href( span(null, array('class' => 'ui-icon ui-extra-icon-code')).' '.gTxt('tagbuilder'), array('event' => 'tag', 'panel' => $event), array('class' => 'txp-tagbuilder-dialog') ) : '' ), array('class' => 'txp-textarea-options') ) ), array('', 'instructions_page_code'), array('class' => 'txp-form-field template'), array('div', 'div') ); return $out; }