Cloning an existing WikiPage

Last edited by JavaWoman:
Modified links pointing to docs server
Mon, 28 Jan 2008 00:15 UTC [diff]


This handler is included in WikkaWiki since version 1.1.6.0
See also:
Documentation: CloneHandlerInfo.


This is the development page for the clone handler.
 



I like to re-use and hate to redesign the wheel... Surely I'm not alone ;-)

Two solutions

Either an action CloneAction or this CloneHandler.
When the action was first published, DarTar explained that a handler would make more sense. It certainly is the right way to do so here is the handler.

Dependancy

This relies on the ExistsPage() function developed by JavaWoman and part of release 1.1.6.0. You can find this version at WikkaDevelopment.

The code

Copy the code into a handler named: clone.php - place it in the handlers/page folder.
Thanks to DarTar, the original code has been improved: please get the correct code below.

How to use it?

Once you have (re)designed a page that fits to be replicated many times (Task Lists, User Pages, Bug Description, Developement Description...) you just have to type in /clone at the end of its URL.
Then you must fill the name of the new WikiPage to be created, you may want to add a note, finally you decide if you just want to create the page or if you want to jump and edit it as soon as it has been copied.

To Do

My code needs probably to be reviewed by expert coder as I am not at all a developper.
Any ideas around this handler more than welcome. Here are a few ideas I have to improve this handler:

All discussions on this page still are at WikiTemplate. We definitely need a PageNameChange handler ;-)
See MovePages - not implemented, it has its problems; but the wish/requirement isn't new ;-) --JavaWoman


Improved CloneHandler


Christian, your code had a number of bugs that I've fixed in a new version of the handler posted below.
I think this is an issue that should be addressed in general, not only in this case. We should try to produce an accessible code also for users whose browser does not support javascript. A central error handler, displaying alert messages (both local, as those produced by handlers and actions, and global, as those produced by the database or the engine itself), would be a major improvement of WikkaWiki.


I've uploaded this handler to the server so please test it and give your feedback here.
-- DarTar

<div class="page">
<?php
/**
 * Clone the current page and save a copy of it as a new page.
 *
 * Usage: append /clone to the URL of the page you want to clone
 *
 * This handler checks the existence of the source page, the validity of the
 * name of the target page to be created, the user's read-access to the source
 * page and write-access to the target page.
 * If the edit option is selected, the user is redirected to the target page for
 * edition immediately after its creation.
 *
 * @package         Handlers
 * @subpackage        
 * @name              clone
 *
 * @author            {@link http://wikka.jsnx.com/ChristianBarthelemy Christian Barthelemy} - original idea and code.
 * @author            {@link http://wikka.jsnx.com/DarTar Dario Taraborelli} - bugs fixed, code improved, removed popup alerts.  
 * @version           0.4
 * @since             Wikka 1.1.6.X
 *                      
 * @input             string  $to  required: the page to be created
 *                            must be a non existing page and current user must be authorized to create it
 *                            default is source page name              
 *
 * @input             string  $note  optional: the note to be added to the page when created
 *                            default is "Cloned from " followed by the name of the source page
 *
 * @input             boolean $editoption optional: if true, the new page will be opened for edition on creation
 *                            default is false (to allow multiple cloning of the same source)
 *
 * @todo              Use central library for valid pagenames.
 *        
 */


// set defaults
$from = $this->tag;
$to = $this->tag;
$note = 'Cloned from '.$from; #i18n
$editoption = '';
$box = 'Please fill in a valid target ""PageName"" and an (optional) edit note.'; #i18n

// print header
echo $this->Format('==== Clone current page ====');

// 1. check source page existence
if (!$this->ExistsPage($from))
{
    // source page does not exist!
    $box = ' Sorry, page '.$from.' does not exist.'; #i18n
} else
{
    // 2. page exists - now check user's read-access to the source page
    if (!$this->HasAccess('read', $from))
    {
        // user can't read source page!
        $box = ' //You aren not allowed to read the source of this page.//'; #i18n
    } else
    {
        // page exists and user has read-access to the source - proceed
        if ($_POST)
        {
            // get parameters
            $to = ($_POST['to'])? $_POST['to'] : $to;
            $note = ($_POST['note'])? $_POST['note'] : $note;
            $editoption = (isset($_POST['editoption']))? 'checked="checked"' : '';
       
            // 3. check target pagename validity
            if (!preg_match("/^[A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*$/s", $to))
            {
                // invalid pagename!
                $box = '""<div class="error">You must specify a valid PageName</div>""'; #i18n
            } else
            {
                // 4. target page name is valid - now check user's write-access
                if (!$this->HasAccess('write', $to))  
                {
                    $box = '""<div class="error">Sorry! You don\'t have write-access to '.$to.'</div>""'; #i18n
                } else
                {
                    // 5. check target page existence
                    if ($this->ExistsPage($to))
                    {
                        // page already exists!
                            $box = '""<div class="error">Sorry! The destination page already exists</div>""'; #i18n
                    } else
                    {
                        // 6. Valid request - proceed to page cloning
                        $thepage=$this->LoadPage($from); # load the source page
                        if ($thepage) $pagecontent = $thepage['body']; # get its content
                        $this->SavePage($to, $pagecontent, $note); #create target page
                        if ($editoption == 'checked="checked"')
                        {
                            // quick edit
                            $this->Redirect($this->href('edit',$to));
                        } else
                        {
                            // show confirmation message
                            $box = '""'.$this->MiniHref('',$to).'"" was succesfully created!'; #i18n
                        }
                    }
                }
            }
        }
        // build form
        $form = $this->FormOpen('clone');
        $form .= '<table class="clone">'.
            '<tr>'.
            '<td><strong>Clone '.$this->Link($this->GetPageTag()).' to:</strong></td>'.
            '<td><input type="text" name="to" value="'.$to.'" size="37" /></td>'.
            '</tr>'.
            '<tr>'.
            '<td><strong>Edit note:</strong></td>'.
            '<td><input type="text" name="note" value="'.$note.'" size="37" /></td>'.
            '</tr>'.
            '<tr>'.
            '<td></td>'.
            '<td>'.
            '<input type="checkbox" name="editoption" '.$editoption.' /> Edit after creation '.
            '<input type="submit" name="create" value="Clone" />'.
            '</td>'.
            '</tr>'.
            '</table>';
        $form .= $this->FormClose();
    }
}

// display messages
if (isset($box)) echo $this->Format(' --- '.$box.' --- --- ');
// print form
if (isset($form)) print $form;
?>
</div>


I've added the feature of letting the user clone the ACLs in this version:
(I also fixed what I thought was a bug, it's now possible to have a blank note for the cloned page.)
Comments are welcome.
/AndreasHeintze
<div class="page">
<?php
/**
 * Clone the current page and save a copy of it as a new page.
 *
 * Usage: append /clone to the URL of the page you want to clone
 *
 * This handler checks the existence of the source page, the validity of the
 * name of the target page to be created, the user's read-access to the source
 * page and write-access to the target page.
 * If the edit option is selected, the user is redirected to the target page for
 * edition immediately after its creation.
 *
 * @package         Handlers
 * @subpackage        
 * @name              clone
 *
 * @author            {@link http://wikkawiki.org/ChristianBarthelemy Christian Barthelemy} - original idea and code.
 * @author            {@link http://wikkawiki.org/DarTar Dario Taraborelli} - bugs fixed, code improved, removed popup alerts.  
 * @version           0.4
 * @since             Wikka 1.1.6.0
 *                      
 * @input             string  $to  required: the page to be created
 *                            must be a non existing page and current user must be authorized to create it
 *                            default is source page name              
 *
 * @input             string  $note  optional: the note to be added to the page when created
 *                            default is "Cloned from " followed by the name of the source page
 *
 * @input             boolean $editoption optional: if true, the new page will be opened for edition on creation
 *                            default is false (to allow multiple cloning of the same source)
 *
 * @todo              Use central library for valid pagenames.
 *        
 */

// defaults
if(!defined('VALID_PAGENAME_PATTERN')) define ('VALID_PAGENAME_PATTERN', '/^[A-Za-zÄÖÜßäöü]+[A-Za-z0-9ÄÖÜßäöü]*$/s');

// i18n
define('CLONE_HEADER', '==== Clone current page ====');
define('CLONE_SUCCESSFUL', '%s was succesfully created!');
define('CLONE_X_TO', 'Clone %s to:');
define('CLONED_FROM', 'Cloned from %s');
define('EDIT_NOTE', 'Edit note:');
define('ERROR_ACL_READ', 'You are not allowed to read the source of this page.');
define('ERROR_ACL_WRITE', 'Sorry! You don\'t have write-access to %s');
define('ERROR_INVALID_PAGENAME', 'This page name is invalid. Valid page names must start with a letter and contain only letters and numbers.');
define('ERROR_PAGE_ALREADY_EXIST', 'Sorry, the destination page already exists');
define('ERROR_PAGE_NOT_EXIST', ' Sorry, page %s does not exist.');
define('LABEL_CLONE', 'Clone');
define('LABEL_EDIT_OPTION', ' Edit after creation ');
define('LABEL_CLONEACLS_OPTION', ' Clone ACLs ');
define('PLEASE_FILL_VALID_TARGET', 'Please fill in a valid target ""PageName"" and an (optional) edit note.');

// initialization
$from = $this->tag;
$to = $this->tag;
$note = sprintf(CLONED_FROM, $from);
$editoption = '';
$cloneacls = '';
$box = PLEASE_FILL_VALID_TARGET;

// print header
echo $this->Format(CLONE_HEADER);

// 1. check source page existence
if (!$this->ExistsPage($from))
{
    // source page does not exist!
    $box = sprintf(ERROR_PAGE_NOT_EXIST, $from);
} else
{
    // 2. page exists - now check user's read-access to the source page
    if (!$this->HasAccess('read', $from))
    {
        // user can't read source page!
        $box = ERROR_ACL_READ;
    } else
    {
        // page exists and user has read-access to the source - proceed
        if (isset($_POST) && $_POST)
        {
            // get parameters
            $to = isset($_POST['to']) && $_POST['to'] ? $_POST['to'] : $to;
            $note = isset($_POST['note']) ? $_POST['note'] : $note;
            $editoption = (isset($_POST['editoption']))? 'checked="checked"' : '';
            $cloneacls = (isset($_POST['cloneacls']))? 'checked="checked"' : '';
       
            // 3. check target pagename validity
            if (!preg_match(VALID_PAGENAME_PATTERN, $to))  //TODO use central regex library
            {
                // invalid pagename!
                $box = '""<em class="error">'.ERROR_INVALID_PAGENAME.'</em>""';
            } else
            {
                // 4. target page name is valid - now check user's write-access
                if (!$this->HasAccess('write', $to))  
                {
                    $box = '""<em class="error">'.sprintf(ERROR_ACL_WRITE, $to).'</em>""';
                } else
                {
                    // 5. check target page existence
                    if ($this->ExistsPage($to))
                    {
                        // page already exists!
                        $box = '""<em class="error">'.ERROR_PAGE_ALREADY_EXIST.'</em>""';
                    } else
                    {
                        // 6. Valid request - proceed to page cloning
                        $thepage=$this->LoadPage($from); # load the source page
                        if ($thepage) $pagecontent = $thepage['body']; # get its content
                        $this->SavePage($to, $pagecontent, $note); #create target page
                        if ($cloneacls == 'checked="checked"')
                        {
                            // Clone ACLs too
                            $acls = $this->LoadAllACLs($from);
                            $this->SaveACL($to, 'read', $acls['read_acl']);
                            $this->SaveACL($to, 'write', $acls['write_acl']);
                            $this->SaveACL($to, 'comment', $acls['comment_acl']);
                        }
                        if ($editoption == 'checked="checked"')
                        {
                            // quick edit
                            $this->Redirect($this->href('edit', $to));
                        } else
                        {
                            // show confirmation message
                            $box = '""<em class="success">'.sprintf(CLONE_SUCCESSFUL, $to).'</em>""';
                        }
                    }
                }
            }
        }
        // build form
        $form = $this->FormOpen('clone');
        $form .= '<table class="clone">'."\n".
            '<tr>'."\n".
            '<td>'.sprintf(CLONE_X_TO, $this->Link($this->GetPageTag())).'</td>'."\n".
            '<td><input type="text" name="to" value="'.$to.'" size="37" maxlength="75" /></td>'."\n".
            '</tr>'."\n".
            '<tr>'."\n".
            '<td>'.EDIT_NOTE.'</strong></td>'.
            '<td><input type="text" name="note" value="'.$note.'" size="37" maxlength="75" /></td>'."\n".
            '</tr>'."\n".
            '<tr>'."\n".
            '<td></td>'."\n".
            '<td>'."\n".
            '<input type="checkbox" name="editoption" '.$editoption.' id="editoption" /><label for="editoption">'.LABEL_EDIT_OPTION.'</label><br />'."\n".
            '<input type="checkbox" name="cloneacls" '.$cloneacls.' id="cloneacls" checked /><label for="cloneacls">'.LABEL_CLONEACLS_OPTION.'</label><br /><br />'."\n".
            '<input type="submit" name="create" value="'.LABEL_CLONE.'" />'."\n".
            '</td>'."\n".
            '</tr>'."\n".
            '</table>'."\n";
        $form .= $this->FormClose();
    }
}

// display messages
if (isset($box)) echo $this->Format(' --- '.$box.' --- --- ');
// print form
if (isset($form)) print $form;
?>
</div>




CategoryDevelopmentHandlers
Comments
Comment by JavaWoman
2004-12-19 22:58:51
Christian, I changed the @since item in your comment block: it cannot be 1.1.5.3 - 'since' indicates as of which version the code was included. Not likely 1.1.6.0 either; change the last bit when it *does* get included.
Comment by JamesMcl
2004-12-24 17:30:36
Christian, I get an error message when trying to clone a page
Call to undefined function: existspage() in ..../handlers/page/clone.php on line 48

Do I have to add any functions to wikka.php ?

Can you help resolve this please.
Comment by AToulouse-152-1-40-106.w82-125.abo.wanadoo.fr
2004-12-24 18:22:32
James, my fault: I should have explained that this relies of the ExistsPage function developed by JavaWoman and part of release 1.1.6.0. You can find this version at WikkaDevelopment (close to the end of the page). I am going to explain this in this page to avoid further confusion. Thanks to you.
Comment by JamesMcl
2004-12-24 19:01:41
Thanks for the explanation. I'll wait for the 1.1.6.0 release which should be soon.
I'm not keen on adding code to wikka as many of the users have different versions and may have included unofficial (yet to be released) modifications.
Comment by JavaWoman
2004-12-24 20:52:53
Christian,
Just a small note: first sentence in the documentation block needs to be terminated with a period (phpDocumentor syntax); same for CloneAction
Comment by MovieLady
2005-03-01 08:44:34
Will it create any holes (security or otherwise) if I change this regex:

// 3. check target pagename validity
if (!preg_match("/^[A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*$/s", $to))

in my local installation to:

/ 3. check target pagename validity
if (!preg_match("/^[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*$/s", $to))

I'm trying to get the clone handler to allow non-wiki page names (which it should do anyway, IMO, since the wikka engine allows them). :)

I looked through the wikka.php file and tried out most of the related regular expressions in it in place of this one, but none of them allowed a non-wiki name as far as my experiments went. This change works just fine, but since I don't know enough about regular expressions to know if it will cause any problems, I figured I'd ask, just in case. :)

Also, FYI: there's a typo in the "page created" confirmation message; "succesfully" should be "successfully."
Comment by VanSciver
2006-03-20 13:54:22
I'm trying to make a list of performances in order by date. As far as naming goes, I first created a page called T20050730 (for Tour July 30th, 2005). That registered fine. But then I decided I want the pages to be named as P20050730 (standing for Performance instead of Tour, to encompass all performances rather than just tour appearances). When I add the /clone handler to the URL and type P20050730, I get an invalid page name, but when I create the page manually, it works just fine. Is it dangerous to have the name as P20050730 ? I had only created one page and don't mind creating the rest, but is it bad to have it named as such when it's technically an invalid page name? (And as a bonus, is there a way to force my pages to just be listed as numbers and no letters?)
Comment by DarTar
2006-03-20 14:03:39
VanSciver, thanks for reporting this issue - the problem arises from an inconsistency between the validation patterns used for creating and for cloning pages. This should be fixed in the next release.
Comment by VanSciver
2006-03-20 14:09:14
Are the page names I'm trying to use valid though? Or can this cause problems when upgrading in the future if a page name is set as P20050730 ?
Comment by DarTar
2006-03-20 14:18:12
If you managed to create a page as of 1.1.6.1, this will still be valid in the future. We should allow more valid characters for page names.
Comment by BrianKoontz
2006-08-22 07:38:32
Can anyone think of a good reason why a valid page name should not/cannot begin with a digit?
Comment by NilsLindenberg
2006-08-22 08:28:37
No, there is none. We just have to built a regexp library so that only one regexp needs to be changed in such cases and it remains consistent througout wikka.
Comment by BrianKoontz
2006-08-22 13:05:28
OK...I was going to make a local change to one of my wikis but figured I'd check here first. Thanks!
Comment by DarTar
2006-08-23 05:06:57
See also:
http://wush.net/trac/wikka/ticket/8
http://wush.net/trac/wikka/ticket/71
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki