taotoo Posted November 12, 2022 Share Posted November 12, 2022 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 More sharing options...
dragan Posted November 13, 2022 Share Posted November 13, 2022 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). 1 Link to comment Share on other sites More sharing options...
taotoo Posted November 13, 2022 Author Share Posted November 13, 2022 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). 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 More sharing options...
dragan Posted November 13, 2022 Share Posted November 13, 2022 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! 2 Link to comment Share on other sites More sharing options...
bernhard Posted November 13, 2022 Share Posted November 13, 2022 I think the best solution would be to ask Ryan to add an option "copyFiles" to the clone method. It would be very easy to add and I think everything that can help with performance is a welcome addition to Ryan ? 2 Link to comment Share on other sites More sharing options...
taotoo Posted November 13, 2022 Author Share Posted November 13, 2022 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). 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 More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now