Jump to content

Clone a Page, but not its image files


taotoo
 Share

Recommended Posts

I'm using a hook (similar to @bernhard's below) to add a Clone button when editing pages.

Is it possible to clone the page, but without also copying over the image files?

I tried adding this code towards the end of the hook:

$clone->image = null;
$clone->save();

This successfully prevents the contents of the image field being cloned, but PW still copies the image files themselves into the new page's directory.

I also looked at the API documentation, but it doesn't seem to provide an option for this.

I want to avoid copying the images as there are typically about 20 files that get copied, which takes about 10 seconds on the shared server. Additionally, the first thing the client will do after cloning the page is delete the images as they will be unneeded.

 

Link to comment
Share on other sites

Basically, you could clone the clone function (ha!) from the core and alter it slightly:

https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/Pages.php#L1733

    function cloneWithoutFiles(Page $page, Page $parent = null, $recursive = true, $options = array()) {

        $defaults = array(
            'forceID' => 0,
            'set' => array(),
            'recursionLevel' => 0, // recursion level (internal use only)
        );

        if (is_string($options)) $options = Selectors::keyValueStringToArray($options);
        $options = array_merge($defaults, $options);
        if ($parent === null) $parent = $page->parent;

        if (count($options['set']) && !empty($options['set']['name'])) {
            $name = $options['set']['name'];
        } else {
            $name = wire()->pages->names()->uniquePageName(array(
                'name' => $page->name,
                'parent' => $parent
            ));
        }

        $of = $page->of();
        $page->of(false);

        // Ensure all data is loaded for the page
        foreach ($page->fieldgroup as $field) {
            // changed:
            if ($field->type !== "FieldtypeImage") {
                if ($page->hasField($field->name)) $page->get($field->name);
            }
        }

        /** @var User $user */
        $user = wire()->wire('user');

        // clone in memory
        $copy = clone $page;
        $copy->setIsNew(true);
        $copy->of(false);
        $copy->setQuietly('_cloning', $page);
        $copy->setQuietly('id', $options['forceID'] > 1 ? (int)$options['forceID'] : 0);
        $copy->setQuietly('numChildren', 0);
        $copy->setQuietly('created', time());
        $copy->setQuietly('modified', time());
        $copy->name   = $name;
        $copy->parent = $parent;

        if (!isset($options['quiet']) || $options['quiet']) {
            $options['quiet'] = true;
            $copy->setQuietly('created_users_id', $user->id);
            $copy->setQuietly('modified_users_id', $user->id);
        }

        // set any properties indicated in options
        if (count($options['set'])) {
            foreach ($options['set'] as $key => $value) {
                $copy->set($key, $value);
                // quiet option required for setting modified time or user
                if ($key === 'modified' || $key === 'modified_users_id') $options['quiet'] = true;
            }
        }

        // tell PW that all the data needs to be saved
        foreach ($copy->fieldgroup as $field) {
            // changed:
            if ($field->type !== "FieldtypeImage") {
                if ($copy->hasField($field)) $copy->trackChange($field->name);
            }
        }

        wire()->pages->cloneReady($page, $copy);
        wire()->cloning++;
        $options['ignoreFamily'] = true; // skip family checks during clone
        try {
            wire()->pages->save($copy, $options);
        } catch (\Exception $e) {
            wire()->cloning--;
            $copy->setQuietly('_cloning', null);
            $page->of($of);
            throw $e;
        }
        wire()->cloning--;

        // check to make sure the clone has worked so far
        if (!$copy->id || $copy->id == $page->id) {
            $copy->setQuietly('_cloning', null);
            $page->of($of);
            return wire()->pages->newNullPage();
        }

        // copy $page's files over to new page
        // changed:
        // basically, you could delete this block altogether
        if (PagefilesManager::hasFiles($page)) {
//            $copy->filesManager->init($copy);
//            $page->filesManager->copyFiles($copy->filesManager->path());
        }

        // if there are children, then recursively clone them too
        if ($page->numChildren && $recursive) {
            $start             = 0;
            $limit             = 200;
            $numChildrenCopied = 0;
            do {
                $children    = $page->children("include=all, start=$start, limit=$limit");
                $numChildren = $children->count();
                foreach ($children as $child) {
                    /** @var Page $child */
                    $childCopy = wire()->pages->clone($child, $copy, true, array(
                        'recursionLevel' => $options['recursionLevel'] + 1,
                    ));
                    if ($childCopy->id) $numChildrenCopied++;
                }
                $start += $limit;
                wire()->pages->uncacheAll();
            } while ($numChildren);
            $copy->setQuietly('numChildren', $numChildrenCopied);
        }

        $copy->parentPrevious = null;
        $copy->setQuietly('_cloning', null);

        if ($options['recursionLevel'] === 0) {
            // update pages_parents table, only when at recursionLevel 0 since parents()->rebuild() already descends
            if ($copy->numChildren) {
                $copy->setIsNew(true);
                wire()->pages->parents()->rebuild($copy);
                $copy->setIsNew(false);
            }
            // update sort
            if ($copy->parent()->sortfield() == 'sort') {
                wire()->sortPage($copy, $copy->sort, true);
            }
        }

        $copy->of($of);
        $page->of($of);
        $page->meta()->copyTo($copy->id);
        $copy->resetTrackChanges();
        wire()->pages->cloned($page, $copy);
        wire()->pages->debugLog('clone', "page=$page, parent=$parent", $copy);

        return $copy;
    }

    // basic usage just as with original function:
    $pg = $pages->get(23506);
    cloneWithoutFiles($pg);

Besides the three places marked with // changed, I also had to replace $this-> with wire()-> to use it in a template file.

Images (files) are not copied to the new page, however, the fields are. It wouldn't be too hard to delete these after you've created the clone (after initial clone-save).

image.thumb.png.f7b610a14f29e2aa8fdd8fbb47a3368b.png

  • Thanks 1
Link to comment
Share on other sites

14 minutes ago, dragan said:

Basically, you could clone the clone function (ha!) from the core and alter it slightly:

https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/Pages.php#L1733

    function cloneWithoutFiles(Page $page, Page $parent = null, $recursive = true, $options = array()) {

        $defaults = array(
            'forceID' => 0,
            'set' => array(),
            'recursionLevel' => 0, // recursion level (internal use only)
        );

        if (is_string($options)) $options = Selectors::keyValueStringToArray($options);
        $options = array_merge($defaults, $options);
        if ($parent === null) $parent = $page->parent;

        if (count($options['set']) && !empty($options['set']['name'])) {
            $name = $options['set']['name'];
        } else {
            $name = wire()->pages->names()->uniquePageName(array(
                'name' => $page->name,
                'parent' => $parent
            ));
        }

        $of = $page->of();
        $page->of(false);

        // Ensure all data is loaded for the page
        foreach ($page->fieldgroup as $field) {
            // changed:
            if ($field->type !== "FieldtypeImage") {
                if ($page->hasField($field->name)) $page->get($field->name);
            }
        }

        /** @var User $user */
        $user = wire()->wire('user');

        // clone in memory
        $copy = clone $page;
        $copy->setIsNew(true);
        $copy->of(false);
        $copy->setQuietly('_cloning', $page);
        $copy->setQuietly('id', $options['forceID'] > 1 ? (int)$options['forceID'] : 0);
        $copy->setQuietly('numChildren', 0);
        $copy->setQuietly('created', time());
        $copy->setQuietly('modified', time());
        $copy->name   = $name;
        $copy->parent = $parent;

        if (!isset($options['quiet']) || $options['quiet']) {
            $options['quiet'] = true;
            $copy->setQuietly('created_users_id', $user->id);
            $copy->setQuietly('modified_users_id', $user->id);
        }

        // set any properties indicated in options
        if (count($options['set'])) {
            foreach ($options['set'] as $key => $value) {
                $copy->set($key, $value);
                // quiet option required for setting modified time or user
                if ($key === 'modified' || $key === 'modified_users_id') $options['quiet'] = true;
            }
        }

        // tell PW that all the data needs to be saved
        foreach ($copy->fieldgroup as $field) {
            // changed:
            if ($field->type !== "FieldtypeImage") {
                if ($copy->hasField($field)) $copy->trackChange($field->name);
            }
        }

        wire()->pages->cloneReady($page, $copy);
        wire()->cloning++;
        $options['ignoreFamily'] = true; // skip family checks during clone
        try {
            wire()->pages->save($copy, $options);
        } catch (\Exception $e) {
            wire()->cloning--;
            $copy->setQuietly('_cloning', null);
            $page->of($of);
            throw $e;
        }
        wire()->cloning--;

        // check to make sure the clone has worked so far
        if (!$copy->id || $copy->id == $page->id) {
            $copy->setQuietly('_cloning', null);
            $page->of($of);
            return wire()->pages->newNullPage();
        }

        // copy $page's files over to new page
        // changed:
        // basically, you could delete this block altogether
        if (PagefilesManager::hasFiles($page)) {
//            $copy->filesManager->init($copy);
//            $page->filesManager->copyFiles($copy->filesManager->path());
        }

        // if there are children, then recursively clone them too
        if ($page->numChildren && $recursive) {
            $start             = 0;
            $limit             = 200;
            $numChildrenCopied = 0;
            do {
                $children    = $page->children("include=all, start=$start, limit=$limit");
                $numChildren = $children->count();
                foreach ($children as $child) {
                    /** @var Page $child */
                    $childCopy = wire()->pages->clone($child, $copy, true, array(
                        'recursionLevel' => $options['recursionLevel'] + 1,
                    ));
                    if ($childCopy->id) $numChildrenCopied++;
                }
                $start += $limit;
                wire()->pages->uncacheAll();
            } while ($numChildren);
            $copy->setQuietly('numChildren', $numChildrenCopied);
        }

        $copy->parentPrevious = null;
        $copy->setQuietly('_cloning', null);

        if ($options['recursionLevel'] === 0) {
            // update pages_parents table, only when at recursionLevel 0 since parents()->rebuild() already descends
            if ($copy->numChildren) {
                $copy->setIsNew(true);
                wire()->pages->parents()->rebuild($copy);
                $copy->setIsNew(false);
            }
            // update sort
            if ($copy->parent()->sortfield() == 'sort') {
                wire()->sortPage($copy, $copy->sort, true);
            }
        }

        $copy->of($of);
        $page->of($of);
        $page->meta()->copyTo($copy->id);
        $copy->resetTrackChanges();
        wire()->pages->cloned($page, $copy);
        wire()->pages->debugLog('clone', "page=$page, parent=$parent", $copy);

        return $copy;
    }

    // basic usage just as with original function:
    $pg = $pages->get(23506);
    cloneWithoutFiles($pg);

Besides the three places marked with // changed, I also had to replace $this-> with wire()-> to use it in a template file.

Images (files) are not copied to the new page, however, the fields are. It wouldn't be too hard to delete these after you've created the clone (after initial clone-save).

image.thumb.png.f7b610a14f29e2aa8fdd8fbb47a3368b.png

Ah - that's how to do it! I was looking in ProcessPageClone.module and couldn't figure out how the files were being copied...

Thank you very much for the comprehensive answer, I'll try it out!

Link to comment
Share on other sites

I guess this should do the trick (delete orphaned images)

// before the function definition:
$imageFields = array(); // hold all image field-names

        // replace/insert in my above function this:
        // tell PW that all the data needs to be saved
        foreach ($copy->fieldgroup as $field) {
            if ($field->type !== "FieldtypeImage") {
                if ($copy->hasField($field)) $copy->trackChange($field->name);
            } else {
                $imageFields[] = $field->name; // here
            }
        }

// test it:
$pg = $pages->get(23506); // clone a page
    $newClone = cloneWithoutFiles($pg);
    $id = $newClone->id;
//    echo "new page id = $id <br>";
    $pg = $pages->get($id);
    $pg->of(false);
    foreach($imageFields as $f) {
        $pg->$f->deleteAll();
        $pg->save($f);
    }

Perhaps there's a cleaner way to do all this... it would be nice to have this as an option built into the core function!

  • Like 2
Link to comment
Share on other sites

1 hour ago, dragan said:

Basically, you could clone the clone function (ha!) from the core and alter it slightly:

https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/Pages.php#L1733

    function cloneWithoutFiles(Page $page, Page $parent = null, $recursive = true, $options = array()) {

        $defaults = array(
            'forceID' => 0,
            'set' => array(),
            'recursionLevel' => 0, // recursion level (internal use only)
        );

        if (is_string($options)) $options = Selectors::keyValueStringToArray($options);
        $options = array_merge($defaults, $options);
        if ($parent === null) $parent = $page->parent;

        if (count($options['set']) && !empty($options['set']['name'])) {
            $name = $options['set']['name'];
        } else {
            $name = wire()->pages->names()->uniquePageName(array(
                'name' => $page->name,
                'parent' => $parent
            ));
        }

        $of = $page->of();
        $page->of(false);

        // Ensure all data is loaded for the page
        foreach ($page->fieldgroup as $field) {
            // changed:
            if ($field->type !== "FieldtypeImage") {
                if ($page->hasField($field->name)) $page->get($field->name);
            }
        }

        /** @var User $user */
        $user = wire()->wire('user');

        // clone in memory
        $copy = clone $page;
        $copy->setIsNew(true);
        $copy->of(false);
        $copy->setQuietly('_cloning', $page);
        $copy->setQuietly('id', $options['forceID'] > 1 ? (int)$options['forceID'] : 0);
        $copy->setQuietly('numChildren', 0);
        $copy->setQuietly('created', time());
        $copy->setQuietly('modified', time());
        $copy->name   = $name;
        $copy->parent = $parent;

        if (!isset($options['quiet']) || $options['quiet']) {
            $options['quiet'] = true;
            $copy->setQuietly('created_users_id', $user->id);
            $copy->setQuietly('modified_users_id', $user->id);
        }

        // set any properties indicated in options
        if (count($options['set'])) {
            foreach ($options['set'] as $key => $value) {
                $copy->set($key, $value);
                // quiet option required for setting modified time or user
                if ($key === 'modified' || $key === 'modified_users_id') $options['quiet'] = true;
            }
        }

        // tell PW that all the data needs to be saved
        foreach ($copy->fieldgroup as $field) {
            // changed:
            if ($field->type !== "FieldtypeImage") {
                if ($copy->hasField($field)) $copy->trackChange($field->name);
            }
        }

        wire()->pages->cloneReady($page, $copy);
        wire()->cloning++;
        $options['ignoreFamily'] = true; // skip family checks during clone
        try {
            wire()->pages->save($copy, $options);
        } catch (\Exception $e) {
            wire()->cloning--;
            $copy->setQuietly('_cloning', null);
            $page->of($of);
            throw $e;
        }
        wire()->cloning--;

        // check to make sure the clone has worked so far
        if (!$copy->id || $copy->id == $page->id) {
            $copy->setQuietly('_cloning', null);
            $page->of($of);
            return wire()->pages->newNullPage();
        }

        // copy $page's files over to new page
        // changed:
        // basically, you could delete this block altogether
        if (PagefilesManager::hasFiles($page)) {
//            $copy->filesManager->init($copy);
//            $page->filesManager->copyFiles($copy->filesManager->path());
        }

        // if there are children, then recursively clone them too
        if ($page->numChildren && $recursive) {
            $start             = 0;
            $limit             = 200;
            $numChildrenCopied = 0;
            do {
                $children    = $page->children("include=all, start=$start, limit=$limit");
                $numChildren = $children->count();
                foreach ($children as $child) {
                    /** @var Page $child */
                    $childCopy = wire()->pages->clone($child, $copy, true, array(
                        'recursionLevel' => $options['recursionLevel'] + 1,
                    ));
                    if ($childCopy->id) $numChildrenCopied++;
                }
                $start += $limit;
                wire()->pages->uncacheAll();
            } while ($numChildren);
            $copy->setQuietly('numChildren', $numChildrenCopied);
        }

        $copy->parentPrevious = null;
        $copy->setQuietly('_cloning', null);

        if ($options['recursionLevel'] === 0) {
            // update pages_parents table, only when at recursionLevel 0 since parents()->rebuild() already descends
            if ($copy->numChildren) {
                $copy->setIsNew(true);
                wire()->pages->parents()->rebuild($copy);
                $copy->setIsNew(false);
            }
            // update sort
            if ($copy->parent()->sortfield() == 'sort') {
                wire()->sortPage($copy, $copy->sort, true);
            }
        }

        $copy->of($of);
        $page->of($of);
        $page->meta()->copyTo($copy->id);
        $copy->resetTrackChanges();
        wire()->pages->cloned($page, $copy);
        wire()->pages->debugLog('clone', "page=$page, parent=$parent", $copy);

        return $copy;
    }

    // basic usage just as with original function:
    $pg = $pages->get(23506);
    cloneWithoutFiles($pg);

Besides the three places marked with // changed, I also had to replace $this-> with wire()-> to use it in a template file.

Images (files) are not copied to the new page, however, the fields are. It wouldn't be too hard to delete these after you've created the clone (after initial clone-save).

image.thumb.png.f7b610a14f29e2aa8fdd8fbb47a3368b.png

This works great, thank you so much!

I noticed that your code is based on a a PW v2.7 Pages.php file. My existing PW v3 Pages.php file had in it only this cloning code:

public function ___clone(Page $page, Page $parent = null, $recursive = true, $options = array()) {
		return $this->editor()->_clone($page, $parent, $recursive, $options);
	}

I just pasted your code in below this, and it works. Not sure if that's what I'm meant to be doing or not.

Where does PW v3 keep all its cloning code?

1 hour ago, dragan said:

I guess this should do the trick (delete orphaned images)

// before the function definition:
$imageFields = array(); // hold all image field-names

        // replace/insert in my above function this:
        // tell PW that all the data needs to be saved
        foreach ($copy->fieldgroup as $field) {
            if ($field->type !== "FieldtypeImage") {
                if ($copy->hasField($field)) $copy->trackChange($field->name);
            } else {
                $imageFields[] = $field->name; // here
            }
        }

// test it:
$pg = $pages->get(23506); // clone a page
    $newClone = cloneWithoutFiles($pg);
    $id = $newClone->id;
//    echo "new page id = $id <br>";
    $pg = $pages->get($id);
    $pg->of(false);
    foreach($imageFields as $f) {
        $pg->$f->deleteAll();
        $pg->save($f);
    }

Perhaps there's a cleaner way to do all this... it would be nice to have this as an option built into the core function!

I found that I could just add the code below to the hook - it seems to prevent the contents of the field being copied over to the new page:

$clone->image = null;
$clone->save();
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...