Skip to content

$pages->clone() leaves stale 'published' and 'namePrevious' from source page on the returned copy #2338

Description

@adrianbj

Short description of the issue

PagesEditor::_clone() leaves stale state from the source page on the in-memory copy it returns: $copy->published keeps the source's publication timestamp (while the copy's own DB row has published=NULL), and $copy->namePrevious is set to the source page's name as a side effect of _clone() assigning the new unique name.

A visible consequence: if API code holds the returned clone object past PagePathHistory's 120s minimumAge and then renames it, PagePathHistory records the source page's live path as history for the clone (e.g. /aaa/ → clone id), because the rename hook sees namePrevious='aaa'. This surfaced while working on #1024 (see discussion there from this comment onward), but the stale in-memory state is a core issue independent of that module.

Expected behavior

The Page object returned by $pages->clone() should describe the copy, not the source:

  • $copy->published should be 0 when the copy has not been published itself (matching its DB row, where published is NULL and is only set by published=NOW() when the page is actually published).
  • $copy->namePrevious should be empty — the copy has never had a previous name.

Actual behavior

  • $copy->published returns the source page's publication timestamp when the source was loaded from the DB.
  • $copy->namePrevious returns the source page's name, because _clone() sets $copy->name = $name while change tracking is active, and Page::trackChange() records the first name change into namePrevious.
  • Downstream effect: renaming that clone object later records the source's live path into page_path_history pointing at the clone.

Verified on a stock 3.0.271 install with a real DB (PagePathHistory installed, clone created unpublished the way ProcessPageClone::cloneAjax() does, object aged past minimumAge, then renamed):

clone: name=ptest-f-1  mem published=1788808049  namePrevious='ptest-f'
history: /ptest-f -> page 47137 (clone id=47137, source id=47136)

The relevant _clone() code initializes created/modified/id/numChildren on the copy but not published, and assigns the name with tracking active:

$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;

Optional: Suggestion for a possible fix

In wire/core/Pages/PagesEditor.php _clone():

$copy->setQuietly('created', time());
$copy->setQuietly('modified', time());
$copy->setQuietly('published', 0); // copy has not itself been published yet

I tested this line on 3.0.271: the in-memory value then matches the DB row, and the PagePathHistory symptom above goes away (in combination with the guard proposed in #1024; the published fix is what makes that guard reliable for held clone objects). Publishing the copy still works normally — published=NOW() is set by the save that publishes it.

For namePrevious, the same treatment would be $copy->setQuietly('name', $name) instead of $copy->name = $name (or clearing namePrevious after assignment), though I've only tested the published line; the name assignment may have other intentional side effects, so I'd defer to Ryan on that half.

Steps to reproduce the issue

  1. Install PagePathHistory. Have a published page, e.g. /aaa/.
  2. In an API script (or Tracy console), load it fresh: $src = $pages->get('/aaa/');
  3. $copy = $pages->clone($src, null, false, ['set' => ['status' => $src->status | Page::statusUnpublished]]); (same as the PageList "copy" action does)
  4. Dump $copy->published (source's timestamp, expected 0) and $copy->namePrevious (aaa, expected empty).
  5. To see the PagePathHistory consequence without waiting 2 minutes: $copy->setQuietly('created', time() - 600); then $copy->of(false); $copy->name = 'bbb'; $pages->save($copy);
  6. SELECT * FROM page_path_history now contains /aaa (the source's live path) pointing at the clone's ID.

Setup/Environment

  • ProcessWire version: 3.0.271 (code unchanged on current dev)
  • PHP version: 8.5.10
  • MySQL version: MariaDB 12.3.2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions