Jump to content

$page->render() acts differently in multilanguage setup because of trackChanges feature


nurkka
 Share

Recommended Posts

Hello everyone,

I use ProcessWire with multilangage support (5 languages) to provide a shop with content as JSON files. To match the specifications regarding the structure of those JSON files, I wrote a module that exports the files on page save. It renders a page and outputs the language content in different JSON files, within different folders.

The module hooks into the Pages::saved hook. As soon as a page is saved in the admin area, the module loops over the languages, renders the page content in each language and writes a JSON file for each language.

The following phenomenon now occurs:

  • If one edits individual fields of the page in the admin area and then saves it, the JSON files are output correctly.
  • If one saves the page again – without having changed a field on the page – $page->render() does not return the text content of the multilanguage fields, but objects with the text content of all languages. These are e.g. LanguagesPageFieldValue objects or ComboLanguagesValue objects.

I believe this must have something to do with the trackChanges behaviour. ProcessWire tracks the changes that have been made to fields. If no changes have been detected, the behaviour appears to be different when saving the page and with $page->render within the Pages::saved hook.

Here is the code (simplified) I am using:

<?php namespace ProcessWire;

class ExportAsJson extends WireData implements Module {

	public function init() {
		$this->addHookAfter('Pages::saved', $this, 'hookPageSaved', ['priority' => 200]);
	}

	protected function hookPageSaved(HookEvent $event) {
		$page = $event->arguments(0); // get the saved page

		// save current settings
		$saved_lang              = $this->wire->user->language;
		$saved_user              = $this->wire->user;
		$saved_output_formatting = $page->of();

		// set current user to guest user
		$this->wire->users->setCurrentUser($this->wire->users->getGuestUser());
		$page->of(true);

		$json_data = [];

		foreach ($this->wire->languages as $l) {
			$lang_name = $l->name;

			// set current language for rendering the page 
			$this->wire->user->language = $l;

			// get rendered json data as string
			$markup = $page->render();

			$json_data[$lang_name] = json_decode($markup);
		}

		// restore saved settings
		$page->of($saved_output_formatting);
		$this->wire->users->setCurrentUser($saved_user);
		$this->wire->user->language = $saved_lang;

		// write the JSON data to separate files on the remote shop server
		$this->writeJsonToSftpAsSeparateFiles($page, $json_data);
	}

}

The correct result in JSON would be like this:

[
	{
		"featureHeadline": "Lorem ipsum dolor"
	}
]

But when I save the page without changing a field beforehand, I get this:

[
	{
		"featureHeadline": {}
	}
]

Does anyone have an idea how to fix this behaviour?

Thanks and best regards!

 

Link to comment
Share on other sites

  • nurkka changed the title to $page->render() acts differently in multilanguage setup because of trackChanges feature

Can you also provide the code for your template file (the one which gets used by the render()-function)?

Also just a side note: Is there a reason you are generating JSON in the render file, decoding it just to pass it along and re-encode it again?

Link to comment
Share on other sites

23 hours ago, poljpocket said:

Can you also provide the code for your template file (the one which gets used by the render()-function)?

Here is an example of the template file:

$json_data = [];

foreach ($page->contentblocks as $cb) { // "contentblocks" is a repeatermatrix field
	$json_data_block = $cb->render(); // this returns an actual json array
	array_push($json_data, $json_data_block);
}

header('Content-Type: application/json');
echo json_encode($json_data);

And here is an examle of a repeatermatrix field template file:

$json_data = [];
$json_data["headline"] = $page->text; // "text" is a multilanguage text field
$json_data["text"]     = $page->body; // "body" is a multilanguage textarea/tinymce field

return $json_data; // no json_encode needed 

 

23 hours ago, poljpocket said:

Also just a side note: Is there a reason you are generating JSON in the render file, decoding it just to pass it along and re-encode it again?

Returning JSON data from the template did not work, because the $page->render() method threw an error ("strlen(): Argument #1 ($string) must be of type string, array given"). So I had to convert the JSON data to a string in the template and convert it back in the module. The advantage is, that one can view the page in the browser and check the JSON output.

Link to comment
Share on other sites

Posted (edited)

Ok, I tried to reproduce your situation locally on Docker. There are two languages "default" and "other". I have used site-blank as template and just added a TinyMCE ML field for body and added it to basic-page. Also, I changed the title field to be ML. PW version is 3.0.227 and PHP version is 8.2.18.

Here's the result after I have changed nothing and just hit "Save" (same result when I change something):

image.thumb.png.2db74140eedeeddef749af8986a4981c.png

And here is the relevant code I used. I tried to stay as true to your code as possible.

ready.php

<?php namespace ProcessWire;

if(!defined("PROCESSWIRE")) die();

/** @var ProcessWire $wire */

$wire->pages->addHookAfter('Pages::saved(template=basic-page)', 'hookPageSaved', ['priority' => 200]);

function hookPageSaved(HookEvent $event) {
    $wire = $event->wire;
    $page = $event->arguments(0); // get the saved page

    // save current settings
    $saved_lang              = $wire->user->language;
    $saved_user              = $wire->user;
    $saved_output_formatting = $page->of();

    // set current user to guest user
    $wire->users->setCurrentUser($wire->users->getGuestUser());
    $page->of(true);

    $json_data = [];

    foreach ($wire->languages as $l) {
        $lang_name = $l->name;

        // set current language for rendering the page 
        $wire->user->language = $l;

        // get rendered json data as string
        $markup = $page->render();

        $json_data[$lang_name] = json_decode($markup);
    }

    // restore saved settings
    $page->of($saved_output_formatting);
    $wire->users->setCurrentUser($saved_user);
    $wire->user->language = $saved_lang;

    // write the JSON data to Tracy
    bd($json_data);
}

basic-page.php

<?php namespace ProcessWire;

/** @var Page $page */

$json_data = [];

$json_data['headline'] = $page->title;
$json_data['text'] = $page->body;

header('Content-Type: application/json');
echo json_encode($json_data);

Note two differences:

  1. I have restricted the hook to only fire for pages with template basic-page (as this would throw a bunch of errors in the Admin otherwise)
  2. I am not using a module to add the hook

Maybe this sheds some light into the situation.

Edited by poljpocket
  • Like 2
Link to comment
Share on other sites

Thanks for testing. I was using the latest stable version of ProcessWire (3.0.229) and upgraded now to the latest dev version (3.0.239). The issue seems to be gone (for now), i.e. I get the same data on saving the page – no matter if I change something in the page editor or not.

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...