Jump to content

Adding a clone button to Page Edit


Robin S
 Share

Recommended Posts

If you have the core ProcessPageClone module installed you can copy a page via Page List:

2020-12-21_150048.png.95af4987daf701c2fbd11d919a2f1bd5.png

 

But sometimes I don't want to hunt around through Page List to find the page I want to copy, and instead I have found the page via the admin search and have it open in Page Edit. So I used this hook in /site/ready.php to add a "Clone this page" button to the bottom of the Settings tab.

2020-12-21_150341.png.55582c1011e29f3755732d0e95435e62.png

// Add a clone button to the Settings tab of Page Edit if this page is allowed to be cloned
$wire->addHookAfter('ProcessPageEdit::buildFormSettings', function(HookEvent $event) {
	$wrapper = $event->return;
	$modules = $event->wire()->modules;
	$page = $event->process->getPage();
	/** @var ProcessPageClone $ppc */
	$ppc = $modules->get('ProcessPageClone');
	if($page && $ppc->hasPermission($page)) {
		/** @var InputfieldButton $f */
		$f = $modules->get('InputfieldButton');
		$f->value = 'Clone this page';
		$f->href = $event->wire()->config->urls->admin . 'page/clone/?id=' . $page->id;
		$wrapper->add($f);
	}
});

Maybe somebody else has this need and finds this useful.

  • Like 12
  • Thanks 1
Link to comment
Share on other sites

Also, I want to open the page in Page Edit after it is cloned. Here is a hook for that:

// Edit the cloned page after it is created
$wire->addHookBefore('ProcessPageClone::execute', function(HookEvent $event) {
	$event->wire()->addHookBefore('Session::redirect', function(HookEvent $event) {
		$url = $event->arguments(0);
		$id = (int) filter_var($url, FILTER_SANITIZE_NUMBER_INT);
		$redirect_url = $event->wire()->config->urls->admin . 'page/edit/?id=' . $id;
		$event->arguments(0, $redirect_url);
	});
});

 

  • Like 7
Link to comment
Share on other sites

  • 4 months later...

This is what I am using:


    $this->wire->addHookAfter('ProcessPageEdit::getSubmitActions', function($event) {
      $page = $event->process->getPage();
      if($page->template != "foo") return;
      $actions = $event->return;
      unset($actions['next']);

      $actions['clone'] = [
        'value' => 'clone',
        'icon' => 'clone',
        'label' => 'Save + create copy',
      ];

      $event->return = $actions;
    });

    $this->wire->addHookAfter('ProcessPageEdit::processSubmitAction', function($event) {
      $action = $event->arguments(0); // action name, i.e. 'hello'
      $page = $event->process->getPage(); // Page that was edited/saved
      if($page->template != 'foo') return;

      if($action === 'clone') {
        $copy = $this->wire->pages->clone($page);
        $copy->title .= ' (copy ' . uniqid() . ')';
        $copy->save();
        $this->wire->session->redirect($copy->editUrl);
      }
    });

 

  • Like 7
  • Thanks 1
Link to comment
Share on other sites

  • 2 years later...

Thanks a lot!

Do you know if someone has ever thought about to insert those kind of features into the core?

*edit*

If sombody may need the same behaviour of Pagelist cloning (with unpublished status), I made a little hack to the @bernhard code:

...
if($action === 'clone') {

    // get original page status
    $status = $page->status();
    // set unpublished
    $page->setStatus(2049);
    $page->save();

    // clone
    $copy = $this->wire->pages->clone($page);

    // get original name and store in an array
    $nameArr = explode('-',$page->name);
    // save cloned name (microtime style + 1)
    $copy->setAndSave('name', $nameArr[0] . '-' . ($nameArr[1] + 1));
    // add ' (copy ... + incremented timestamp)' to title
    $copy->setAndSave('title', $page->title . ' (copy ' . ($nameArr[1] + 1) . ')');
    // set unpublished status
    $copy->setStatus(2049);
    $copy->save();

    // restore original status to original page
    $page->setStatus($status);
    $page->save();

    $this->wire->session->redirect($copy->editUrl);

}
...

N.B.

I used this trick (unpublishing the original page) due a personal hook that checks pages with same title (and warn if found one or more dupicates). Without the unpublished status for the original page, it seems that at first saving the title is the same also for cloned item... 

The same results is obtained with the @Robin S hook, but flagging "Keep page unpublished" in the cloning mask.

Link to comment
Share on other sites

Hey @Cybermano what exactly are you trying to do?

If you only want to make the clone unpublished you can add this line before $copy->save():

$copy->addStatus(Page::statusUnpublished);

I've some questions and suggestions for improvements on your code (assuming that the only difference to my old post is that the cloned page should be unpublished):

  • Why do you set $page to unpublished during clone and then restore the previous state? That's 2x unnecessary page saves.
  • You are using setAndSave for name + title, but you save() the $copy later anyhow. That means you produce 3 save operations instead of one. Just use $copy->set('name', ...) and same for title and changes will be stored on save()
  • You are using setStatus(). It should be fine but I think it's better to use addStatus() in general unless you really want to force a single status. For example pages can have a "corrupted" status and that will be removed on setStatus() but will be kept when using addStatus() - please anybody correct me if I'm wrong 🙂 
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Hi @bernhard, thank you for your suggestions and for your time too.

You are right, there are unecessary $page->save(), but:

let's say that I have a hook that check if there are pages with the same title (on page saved).

Spoiler
// CHECK TITLE DUPLICATES - ONLY FOR CURRENT YEAR
if (wire('user')->isLoggedin()){
    $wire->addHookBefore('Pages::saved(template=foo)', function ($event){
    $page = $event->arguments[0];
    $alreadyPresent = [];
    $message = '';
		// get items to check
        $items = wire('pages')->find('template=foo, parent.title='.date('Y'));
            foreach ($items as $item){
				// store in array if not already present
                if ( !array_key_exists(date('Y').'_'.$item->title, $alreadyPresent) ){
                    $alreadyPresent[date('Y').'_'.$item->title] = $item->title;
                } else {
					// for SuperUsers create links for id (to open page for editing)
                    if($item->editable()) {
                        $message .= '<b>'.$item->title.'</b> already exists for this year ('.date('Y').')';
                        $duplicates = wire('pages')->find('template=foo, title='.$item->title.', parent.title='.date('Y'));
                        $links = '';
                        $counter = 1;
                        foreach ($duplicates as $d){
                            if ($d->editable()){
                                $links .= '<a href="'.$d->editUrl.'" target="_blank">'.$d->id.'</a>';
                            } 
                        if ($counter < count($duplicates)){
                            $links .= ' | ';
                        }                        
                        $counter++;    
                        }
                        $message .= ' [ page ids: '.$links.' ] ';
                    } else {
                        // for safe-editors (without editing permission)
                        $message .= '<b>'.$item->title.'</b> already exists for this year ('.date('Y').') [ page ids: '.wire('pages')->find('template=foo, title='.$item->title.', parent.title='.date('Y')).' ] ';
                    }
                }
            }
        if ($message != '') {
            $page->warning($message, Notice::allowMarkup);
        }
    });
}

We have a site where guests can subscribe themselve on each year as art performers for yearly event, but only once.
The subscription form is a FormBuilder form with manual send to page (to double check some contents filled in by guests, such as descriptions, social profile links or loaded images). 
We have hundreds of requests every year, and we can't remember the name of all; so I made the previous hook that works fine.

Well, this hook warns every time on cloning despite the title changing.

The only way that I have found to not be warned is to set the original page as unpublished and, after cloning, to restore the original status.
Otherwise with RobinS's hook, but activating "Make the new page unpublished" on cloning mask (but this is a one more passage respect your hook).

Maybe I missing something or I making any mistake...

Link to comment
Share on other sites

@Cybermano It should be quite easy to ignore cloned pages in your hook:

$wire->addHookAfter('Pages::saved', function ($event) {
    $page = $event->arguments(0);
    if ($page->_cloning) return;
    bd($page);
});

Use tracy debugger to inspect the saved page objects and you can see if there are any helpful properties (like _added for example).

  • Thanks 1
Link to comment
Share on other sites

10 hours ago, bernhard said:

@Cybermano It should be quite easy to ignore cloned pages in your hook:

$wire->addHookAfter('Pages::saved', function ($event) {
    $page = $event->arguments(0);
    if ($page->_cloning) return;
    bd($page);
});

Use tracy debugger to inspect the saved page objects and you can see if there are any helpful properties (like _added for example).

I will surely do. Many thanks.

 

Thanks again @bernhardperfect as always, of course.

I will indagate the _added prop as suggested (maybe there is the reason that fires my hook without the _cloning return).

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...