Problem/Motivation

When cloning a page variant, the cloned one share the same UUID of the original one.
Worse, also the internal panes share the same UUID of the panes of the original variant. For panes, sometimes, it is right to keep the UUID. For example if the pane is a "reusable custom content pane" we would like to keep the original UUID because it's reusable and we want to load the same content. But, for example with simple custom content we need a new UUID because it's a totally independent new content and it has nothing to do with the original one.

This bug makes variants with the same UUID to be skipped when refreshing strings during the translation process.
In i18n_panels this is done by the following lines of i18n_panels.module (function: i18n_panels_i18n_string_list):

// Avoid duplicated runs _18n_panels_fetch_all_panel_displays() probably
// returns the same display twice, one for the db based and one for the
// exported one.
if (isset($strings['panels']['display_configuration'][$display->uuid])) {
  continue;
}

The previous code is correct assuming that the UUID is unique. If you clone a variant it's not!

I found out this bug working with i18n_panels, but it's not only related to i18n_panels. It's a more general problem and it must be solved. So, I will mark it as major.

Steps to reproduce the issue

  • Enable panels, page_manager, i18n_panels.
  • Create a page.
  • Create a variant.
  • Clone the variant.

Proposed resolution

An approach could be solve the issue when saving the display (panels_save_display).
We can think a way to set the did of the panel to "cloned" when it's cloned so we can define a complete different procedure for this case.

Workaround

As a temporary workaround for any issue related to this and translation of custom content proceed as follows:

  • Add custom content
  • Choose PHP as text format
  • Implement the translation with a switch case on the global $language variable.

Attached is an export of a simple page that exposed the problem.
To create the exported panel I followed the steps defined above.

What do you think about that?

Please, any suggestion or thought about this issue is well accepted.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

goldenboy’s picture

Issue summary: View changes
jweowu’s picture

Yes, this was the notable unresolved issue in #1277908: Introduce UUIDs onto panes & displays for better exportability & features compatibility, and I would love to see some progress made.

I think the final question is: how do we prevent uuids from being duplicated (e.g. by cloning or importing a display), while still ensuring that they remain unchanged in other circumstances.

das-peter solved some of the issues at the time, and sdboyer's comments (quoted below) were the closest thing we had to a plan for moving forward on the remaining aspects; but we didn't get any further in confirming a solution and working out the exact implementation details.

I've not re-examined the issue (or your suggestion) at this point, but I wanted to cross-reference the issues (I've added this one as a related issue for the original, to try to increase its visibility).

in the case of cloning, it is pretty clear that the goal is to produce a copy, which means that the uuids should change as the identifier linkage between the cloned object and its source should be broken. with imports, however, it is less clear. i haven't entirely thought this through, but my gut says that we should add a checkbox for either retaining or regenerating uuids on import to all of the import forms, then have the behavior default to regeneration.

also, with such discussions such as #1557842: Allow Feature module exports to add Panes to Existing Panels, it becomes more complicated because we may want to have pane uuids stay the same while the display uuid changes. we don't have to solve that problem in this issue for it to be committed, but it is something people invested in this issue should be aware of.

geek-merlin’s picture

Here is some quickly knitted code that fixes duplicate UUIDs by selecting duplicate ones, unsetting and re-saving them.

It might be the base for a "fix uuids" button. Be sure to "refresh user-defined strings" afterwards.

// PIDS: Get all pids with dup uuid
$query=db_query('select uuid, pid, did from {panels_pane} where uuid in (select uuid from {panels_pane} group by uuid having count(pid)>1) order by uuid;');
// Group pids by uuid
$uuid2pids = array();
foreach ($query as $row) {
  $uuid2pids += array($row->uuid => array());
  $uuid2pids[$row->uuid][] = $row->pid;
  $pids2dids[$row->pid] = $row->did;
}
// Iterate over uuid groups and leave out first pid.
$fix_did2pids = array();
foreach ($uuid2pids as $uuid => $pids) {
  array_shift($pids);
  foreach ($pids as $pid) {
    $did = $pids2dids[$pid];
    $fix_did2pids += array($did => array());
    $fix_did2pids[$did][] = $pid;
  }
}
// DIDS: Get all dids with dup uuid
$query=db_query('select uuid, did from {panels_display} where uuid in (select uuid from {panels_display} group by uuid having count(did)>1) order by uuid;');
// Group dids by uuid
$uuid2dids = array();
foreach ($query as $row) {
  $uuid2dids += array($row->uuid => array());
  $uuid2dids[$row->uuid][] = $row->did;
}
// Iterate over uuid groups and leave out first did.
$fix_did2did = array();
foreach ($uuid2dids as $uuid => $dids) {
  array_shift($dids);
  foreach ($dids as $did) {
    $fix_did2did[$did] = $did;
  }
}
// Now do the work!
$fix_dids = array_keys($fix_did2did + $fix_did2pids);
$displays = panels_load_displays($fix_dids);
foreach ($displays as $did => $display) {
  if (isset($fix_did2did[$did])) {
    unset($display->uuid);
  }
  if (isset($fix_did2pids[$did])) {
    foreach ($fix_did2pids[$did] as $pid) {
      unset($display->content[$pid]->uuid);
    }
  }
  panels_save_display($display);
}
dpm(get_defined_vars());

das-peter’s picture

Status: Active » Needs review
FileSize
1.96 KB

Here's a patch that re-generates the uuids when cloning.
I'll open a related issue for page manager.
The patch adds the method panels_display::clone_display(), which takes care of re-generating all the uuids.
Maybe we even could handle this in the magic method __clone() - but I'm not sure if there are cases in which clone is used just to decouple one from another instance for other purposes.

sylus’s picture

Yeah this issue has tripped me up as well. Particularly when you are doing translations for a cloned display that is different. As the string context will be the same so the translation will effect all cloned panels.

chris.guitarte’s picture

I noticed that the patch does not fix the use-case where we have modified a system page manager page (such as user_view).

When I try to clone the variant under /admin/structure/pages/nojs/operation/user_view/handlers/user_view__custom_user_profile_standard/actions/clone

It simply renames the variant presumably because the UUIDs have not been re-generated.

das-peter’s picture

@usc.guitarte Since page manager is part for ctools it's not part of this patch. Please see the related child issue: #2449245: Cloning page manager handlers leads to duplicated uuids.

ndf’s picture

Status: Needs review » Needs work

Think importing needs same behaviour.

So exporting and importing a variant should behave equally as cloning a variant.
Unless you explicitly import with an option like 'overwrite existing variant if it exists'. The UUID can act as identifier for this.

Patch for cloning works well though.

To me this bug can also be a critical one, because translations just don't work. Try to translate a 'manually set Title' for instance one cloned panel-variants/panels.

das-peter’s picture

Think importing needs same behaviour.

Could we do this in a next step?
I think this one was open quite long now and if it's working I'd like to get it in asap.

geek-merlin’s picture

Status: Needs work » Reviewed & tested by the community

+1 for #9, rtbc as of #8.

@ndf: feel free to open a followup.

ndf’s picture

das-peter’s picture

@ndf Thanks, will follow the other issue too.

DamienMcKenna’s picture

Anybody’s picture

+1 for applying this patch on the next release. The problem is really bad and still exists. Thank you all for your work on it!
Thank you for the script in #3 also.

Confirming the script in #3 and the patch to work. Perhaps the script above should be cleaned and added as hook_update script? Or better not?

DamienMcKenna’s picture

DamienMcKenna’s picture

The problem was also being triggered by panels_save_display() when the display is cloned, which happens in Panelizer quite a bit.

DamienMcKenna’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
3.03 KB

This patch also fixes the tests in the two Panelizer issues referenced above.

DamienMcKenna’s picture

Related issues: +#2750545: New displays saved via panels_save_display() don't reset the UUID

I've moved the panels_save_display() issue to #2750545: New displays saved via panels_save_display() don't reset the UUID to keep it separate.

DamienMcKenna’s picture

This wasn't added to 3.6 but it should be added to 3.7.

dsnopek’s picture

Moving to 3.8 so we can get 3.7 out quicker!

weri’s picture

Status: Needs review » Needs work

I tested the patch and it generates new uuid's for cloned variants, that's fine. But a problem exists, when we add panel pages to features. Every time I save a variants content, also when nothing is changed, the export gets new uuid's for the panel display. This was not a problem before we used i18n_panels to translate the content of panels, because the translate context contains the uuid (e.g.: pane_configuration:64d4bce6-f0cc-4885-8447-eb2b23631595:body) and the key has to be the same otherwise we loose the translation every time we update a panel.

The problem seems to be in the following part od the code:

 function panels_save_display(&$display) {
   $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();

   // If the UUID is invalid or the display is not being updated, i.e. it is
   // being cloned, set/reset the display's UUID.
   if (empty($update) || empty($display->uuid) || !ctools_uuid_is_valid($display->uuid)) {
     $display->uuid = ctools_uuid_generate();
   }

When I save or revert an existing panel, always a new uuid is generated, because $update has the value 'new' and is not a numeric value.

DamienMcKenna’s picture

@weri: That is a completely valid concern. Poop. I won't have time to work on this before Monday, so I'm removing it from the list for consideration.

geek-merlin’s picture

@weri: yes right!
(thanks for the code snippet).

Background: Contrary to Entity API (which also has DB entries for reverted objects), Ctools does NOT have DB entries for reverted objects, only for overridden ones. So any reverted panel will NOT have a $did.

So in the above code we have to distinguish between 2 casesn when $did is not numeric:
a) panel is cloned
b) panel is overridden

(Maybe this is not possible in this code place, then we have to create or erase the UUID earlier on cloning.)

sylus’s picture

Just attaching a new patch since old one won't apply to new panels release 3.9.

khaldoon_masud’s picture

The fix wasn't included in 7.x-3.9.