<?php
/**
 * This script breaks a page in several editable sections.
 * 
 * @author Patrick R. Michaud <pmichaud@pobox.com> 
 * @author John Rankin <john.rankin@affinity.co.nz>
 * @author Sebastian Siedentopf <Schlaefer@macnews.de>
 * @version 1.5
 * @link http://www.pmwiki.org/wiki/Cookbook/BreakPage http://www.pmwiki.org/wiki/Cookbook/SectionEdit
 * @copyright by the authors 2004-2005
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @package break-page
 */

/*
For the Admins
==============
Requirements
------------
* Requires PmWiki 2beta44 and higher
* Last tested on PmWiki 2beta55

Install
-------
1. put this script into your cookbook folder
2. include it into your config.php: "include_once("$FarmD/cookbook/break-page.php");"
3. in scripts/forms.php change line:
        if ($action != 'edit') return; 
into
        if ($action != 'edit' && $action != 'editpage') return; 

Customization
-------------
### Layout
The position and layout of the section links are controlled by the div.breakpage class in 
the script. You should also look if a "float:right;" looks better for you.

### XLPage Strings
* $[(Edit Section &#x2193;)]
* $[Section] x $[of] y


Usage
=====
To break a page use "====" on your wikipage *before* the wanted section. 

If you set $BreakPageAuto a page will addionaly split by the headers. The ====
have only effect in the header they are used in.

For Developers
==============
Version History
---------------
* 1.5 - 2005-08-28 - Schlaefer
** [feature] BreakPageAuto and ==== can be used together in sophisticated way
* 1.4.4 - 2005-08-25 - Schlaefer
** [bugfix] set $HandleAuth to respect password protection
** [change] XLPage string $[(Edit Section &#x2193;)] contains the paratheses now
** [feature] after edit the page jumps to the last edited section
* 1.4.3 - 2005-08-22 - Schlaefer
** BreakPageAuto Bugfix: included sides don't get wikilinks
** some code cleanup
* 1.4.2 - 2005-08-22 - Schlaefer
** BreakPageAuto: new regex string. starting no header text no longer supported
        because of wikistyles and other posible (::) Markup in the page that would cause
        an editlink for no obvious reason 
* 1.4.1 - 2005-08-21 - Schlaefer
** Bugfix: BreakPageAuto: starting no header text sections are recognized
** Bugfix: BreakPageAuto: starting header are recognized
** Bugfix: BreakPageAuto (:nosections:) working 
** code commenting and cleaning 
* 1.4 - 2005-08-21
** much improved BreakPageAuto implementation - Schlaefer
* * 1.3 - 2005-08-21
** first BreakPageAuto Implementation - Schlaefer
* 1.2 - 2005-08-21
** code cleanup, comments, localization - Schlaefer
* 1.1 - 2005-08-04
** PmWiki 2b54 compatibility - Schlaefer
* 1.0 - inital realease
*/

if (!defined('PmWiki'))
        exit ();

/**
 * cookbook break-page is included
 * and running on this pmwiki installation
 */
define('break-page', 1);

SDV($HandleAuth['editpage'] ,'edit');
//layout of the section edit links
SDV($HTMLStylesFmt['editpage'], "
div.breakpage { text-align: right;font-size:smaller;}
");

/**
 * This function searches for the "====" oder header markup on the page and replaces it
 * with the appropriate edit links. This is performed on the whole wiki page at once.
 * 
 * @param string $pagename the name of the page on which section edit links should be inserted
 * @param string $text the text of $pagename 
 * @return string returns the text with all section edit links on it    
 * @see HandleEditPage
 */
function PageBreak($pagename, $text) {
        global $BreakPageAuto, $InclCount;

        //we do not generate (false) links for included sites
        if (preg_match("/\(:(nosections)/", $text) || $InclCount > 0)
                return $text;

        if ($BreakPageAuto) {
                $p = preg_split('/((?<=header:\))|(?m:^))((?=!{1,6})|(?=====))/', $text."\n\n");
                $p = insertblanksectionfct($p);
        } else {
                $p = explode('====', $text."\n\n");
        }

        $n = count($p);

        //creates the editlinks
        for ($i = 1; $i <= count($p); $i ++) {
                $editlink[] = FmtPageName("<a name='section$i'></a><a href='\$PageUrl?p=$i&action=editpage'>$[(Edit Section &#x2193;)]</a>", $pagename);
        }

        //output of editlinks and sections
        $out2[] = $p[0];
        for ($i = 1; $i < count($p); $i ++) {
                $out2[] = Keep("<div class='breakpage'>$editlink[$i]</div>");
                $out2[] = "(:nl:)".$p[$i];
        }
        return (implode('', $out2));
}

/** 
 * Handles the edit, preview and saving of page sections.
 * 
 * It is derived from the standard HandleEdit() function defined in pmwiki.php.
 * 
 * @param string $pagename the currently edited site
 * @param string $auth 
 * @see HandleEdit
 * @see PageBreak
 */
function HandleEditPage($pagename, $auth = 'edit') {
        global $IsPagePosted, $EditFields, $ChangeSummary, $EditFunctions, $FmtV, $Now, $HandleEditFmt;
        global $PageStartFmt, $PageEditFmt, $PagePreviewFmt, $PageEndFmt, $GroupHeaderFmt, $GroupFooterFmt;
        global $PageEditForm, $EnablePost, $InputTags, $BreakPageAuto;

        // we need some additional values in the edit form for section editing.
        // To respect Site.EditForm we replace the standard PmWiki edit form
        // e_form defined in /scripts/form.php with this 
        $InputTags['e_form'] = array (":html" => "<form method='post' action='\$PageUrl?action=editpage'>
                                                                                                <input type='hidden' name='action' value='editpage' />
                                                                                                <input type='hidden' name='n' value='\$FullName' />
                                                                                                <input type='hidden' name='basetime' value='\$EditBaseTime' />
                                                                                                <input type='hidden' name='prechunk' value=\"\$PreChunk\" />
                                                                                                <input type='hidden' name='p' value='\$PNum' />
                                                                                                <input type='hidden' name='postchunk' value=\"\$PostChunk\" />");

        /* standard code from HandleEdit()*/
        if ($_REQUEST['cancel']) {
                Redirect($pagename);
                return;
        }
        Lock(2);
        $IsPagePosted = false;
        $page = RetrieveAuthPage($pagename, $auth, true);
        if (!$page)
                Abort("?cannot edit $pagename");
        PCache($pagename, $page);
        $new = $page;

        /*splits the page text and sets the currently edited section*/
        if ($BreakPageAuto) {
                $p = preg_split('/((?<=header:\))|(?m:^))((?=!{1,6})|(?=====))/', $new['text']);
                $p = insertblanksectionfct($p);
        } else {
                $p = explode('====', $new['text']."\n\n");
        }

        $n = @ $_REQUEST['p'];
        if ($n < 1)
                $n = 1;
        if ($n > count($p))
                $n = count($p);

        //we use this anchor to jump to the last edited section in browser after page storage
        //and save it here, because it could be overwritten by $nnew
        $htmlanchor = $n;

        /* here all the include subheaders when editing a top header takes place*/
        if ($BreakPageAuto) {
                //if a section after the current section "A" exist and "A" is a header
                if (isset ($p[$n]) && preg_match('/^(!{1,6})[^!]/', $p[$n -1], $sn)) {
                        //look if there are sections "B" after "A" which are headers
                        for ($k = $n;($k < count($p) && !preg_match('/^(!{1,6})[^!]/', $p[$k], $u)); $k ++);
                        //if following sections are no header, all following sections are included;
                        //this will happen only for the last header on a side followed only by ====s
                        if (!isset ($u[1]))
                                $u[1] = $sn[1]."it will be longer than sn[1]";
                        $sn1 = strlen($sn[1]);
                        //if the next found headers "B" is a level under current header "A"
                        if ($sn1 < strlen($u[1])) {
                                //we only want to split the page down to header level "A" now, but we need to know
                                //which section "A" would be, if we split the page only down to this level
                                //$nnewin will contain this new section number
                                $nnew = 1;
                                for ($i = 2; $i <= $n; $i ++) {
                                        if (preg_match("/^!{1,".$sn1."}[^!]/", $p[$i -1]))
                                                $nnew += 1;
                                }
                                // now we really split the whole text again until the header level of "A" 
                                // and we set the section number to match this new split style
                                $p = preg_split("/((?<=header:\))|(?m:^))(?=!{1,".$sn1."}[^!])/", $new['text']);
                                $n = $nnew;
                        }

                }
        }

        $new['text'] = $p[$n -1];

        /* standard code from HandleEdit()*/
        foreach ((array) $EditFields as $k)
                if (isset ($_POST[$k]))
                        $new[$k] = str_replace("\r", '', stripmagic($_POST[$k]));
        if ($ChangeSummary)
                $new["csum:$Now"] = $ChangeSummary;

        /*if a preview previously took place the currently not edited text sections are obtained*/
        $PageChunks = array ('prechunk', 'postchunk');
        foreach ((array) $PageChunks as $c) {
                $$c = '';
                if (@ $_POST[$c])
                        $$c = str_replace("\r", '', stripmagic($_REQUEST[$c]));
        }

        if (@ $_POST['post']) //the currently not edited sections are added
                $new['text'] = $prechunk."\n".$new['text']."\n".$postchunk;
        elseif (@ $_POST['preview']) { //page header contains info which section is edited
                if ($n > 1)
                        $GroupHeaderFmt = '';
                $GroupHeaderFmt .= '=&gt;($[Section] '.@ $_REQUEST['p'].' $[of] '.count($p).')(:nl:)';
                if ($n < count($p))
                        $GroupFooterFmt = '';
        }
        elseif ($BreakPageAuto) {
                /* if the section is edited, the not edited sections go into $prechunk and
                 * $postchunk and retained here while editing/previewing until saving the whole page            
                 */
                for ($i = 1; $i <= count($p); $i ++) {
                        if ($i < $n)
                                $prechunk .= $p[$i -1];
                        elseif ($i > $n) $postchunk .= $p[$i -1];
                }
        } else {
                for ($i = 1; $i <= count($p); $i ++) {
                        if ($i < $n)
                                $prechunk .= $p[$i -1].'====';
                        elseif ($i > $n) $postchunk .= '===='.$p[$i -1];
                }
        }

        /* standard code from HandleEdit()*/
        $EnablePost &= (@ $_POST['post'] || @ $_POST['postedit']);
        foreach ((array) $EditFunctions as $fn)
                $fn ($pagename, $page, $new);
        Lock(0);
        if ($IsPagePosted && !@ $_POST['postedit']) {
                Redirect($pagename, "\$PageUrl#section".$htmlanchor);
                return;
        }
        $FmtV['$DiffClassMinor'] = (@ $_POST['diffclass'] == 'minor') ? "checked='checked'" : '';
        $FmtV['$EditText'] = str_replace('$', '&#036;', htmlspecialchars(@ $new['text'], ENT_NOQUOTES));
        $FmtV['$EditBaseTime'] = $Now;

        /*additional FmtV for this script*/
        $FmtV['$PreChunk'] = str_replace('"', '&quot;', str_replace('$', '&#036;', htmlspecialchars($prechunk, ENT_NOQUOTES)));
        $FmtV['$PostChunk'] = str_replace('"', '&quot;', str_replace('$', '&#036;', htmlspecialchars($postchunk, ENT_NOQUOTES)));
        $FmtV['$PNum'] = $n;

        /* standard code from HandleEdit() */
        if ($PageEditForm) {
                $form = ReadPage(FmtPageName($PageEditForm, $pagename), READPAGE_CURRENT);
                $FmtV['$EditForm'] = MarkupToHTML($pagename, $form['text']);
        }
        SDV($HandleEditFmt, array (& $PageStartFmt, & $PageEditFmt, & $PageEndFmt));
        PrintFmt($pagename, $HandleEditFmt);

}

/** 
 * Inserts a pseudo ====.
 *  
 * If a header "A" is followed by Text1, ==== and Text2 an additional
 * ==== ist set between "A" and Text1.
 * 
 * @param array $p splited orignal text
 * @return array the splited text with additional ==== split 
 * @since 1.5 - 2005-08-28
 */
function insertblanksectionfct($p) {
        $q = array ();
        for ($i = 0; $i < count($p); $i ++) {
                if (preg_match("/^====/", $p[$i +1]) && preg_match("/^!/", $p[$i])) {
                        $t = preg_split('/(^!{1,6}.*?\n)/', $p[$i], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
                        $q = array_merge($q, $t);
                } else
                        $q = array_merge($q, $p[$i]);
        }
        return $q;
}

if ($action == 'browse') {
        if ($BreakPageAuto){
                Markup('editsectlinkgen', '<include', '/^.*(([\n]!{1,6})|(====)).*$/se', "PageBreak(\$pagename,PSS('$0'))");
                Markup('removebreakpage', 'directives', '/====/', '');
        }
        else {
                Markup('editseclinkgen', '>include', '/^.*====.*$/se', "PageBreak(\$pagename,PSS('$0'))");
        }
}
elseif ($action == 'edit' || $action == 'editpage') {
        Markup('editsect', '>include', '/====/', "\n<div class='breakpage'>&mdash; === &mdash;</div>\n");
        if ($action == 'editpage')
                $HandleActions['editpage'] = 'HandleEditPage';
} else
        Markup('removebreakpage', 'directives', '/====/', '');
Markup('nosections', 'directives', '/\\(:nosections:\\)/', "");
?>