Jump to content

How to copy field values of a repeater from the parent page to a child page?


Juergen
 Share

Recommended Posts

This is a strange line. 

  $k->title = $page->title;

You child pages will all have the same title; and that title is identical to the parent page. The child pages themselves will have different names; ProcessWire will ensure that. However, I don't know if the same applies to the repeater pages. My suspicion is that this is what is causing the 'integrity constraint' error. I haven't tested yet.

Link to comment
Share on other sites

2 minutes ago, kongondo said:

This is a strange line. 


  $k->title = $page->title;

You child pages will all have the same title; and that title is identical to the parent page. The child pages themselves will have different names; ProcessWire will ensure that. However, I don't know if the same applies to the repeater pages. My suspicion is that this is what is causing the 'integrity constraint' error. I haven't tested yet.

i haven't been following thoroughly and maybe PW should be dealing with this, but this is what I used to manually generate unique repeater names for the PageTableToRepeaterMatrix action:

    private function getUniqueRepeaterPageName() {
        static $cnt = 0;
        return str_replace('.', '-', microtime(true)) . '-' . (++$cnt);
    }

Maybe this is helpful?

Link to comment
Share on other sites

Maybe that could be the reason!!! My child pages have the same title because it is always the same event (only the starting date will change).  I will try it to add the number at the end (fe. event-1, event-2,...).

I have manipulated the path names to add the starting date at the end (event-21-12-1016, event-28-12-1016 and so on). Therefore I never had a problem to store sub events with the same title.

I will give it a try :)

Link to comment
Share on other sites

Actually, it makes no difference. I have tested in PW 2.7 and 3.0.42, with each having the same title and it still works. Both adding to repeaters and deleting child pages and their respective repeaters work. Could you post your Hook code itself? i.e. how you are hooking.

Link to comment
Share on other sites

This is the hook code:

    public function init()
    {
        $this->pages->addHookBefore('saveReady', $this, 'createEvents');
    }

    public function createEvents($event)
    {
        $page = $event->arguments[0];

        //create multiple recurring events - daily,weekly,monthly,yearly
        if (($page->template == "events") AND ($page->repeattype >= "2")) { //check for multiple events
            $pages = $this->wire('pages');
            $repeaterID = $this->wire('fields')->get('eventpricerepeater')->id;
            // this is the 'repeater_field_name' page
            $repeaterPage = $pages->get("parent.name=repeaters, name=for-field-$repeaterID");
            // check if recurring is checked on the page beeing saved.
            if ($page->recurring == 1) {
                foreach ($page->children('include=all') as $child) {
                    $id = $child->id;
                    // first delete the child page	
                    $child->delete();
                    // this is the 'name_of_page_with_repeater'	
                    $childRepeaterPage = $repeaterPage->child("name=for-page-$id");
                    // will also delete the repeate item pages, e.g. the '1234567890'
                    $this->message("ID: {$childRepeaterPage->id} erstellt.");	
                    if($childRepeaterPage->id) $pages->delete($childRepeaterPage, true);
                }
                //START RECURRING DATE
                $eventstartunformatted = $page->getUnformatted('date_start');
                $eventstarttime        = date("H:i", $eventstartunformatted);
                function timeToSeconds($time)
                {
                    $timeExploded = explode(':', $time);
                    if (isset($timeExploded[2])) {
                        return $timeExploded[0] * 3600 + $timeExploded[1] * 60 + $timeExploded[2];
                    }
                    return $timeExploded[0] * 3600 + $timeExploded[1] * 60;
                }
                $eventstartseconds           = timeToSeconds($eventstarttime);
                
                
                //END RECURRING DATE
                $eventendunformatted = $page->getUnformatted('date_end');
                $eventendtime        = date("H:i", $eventendunformatted);
                $diff                = $eventendunformatted - $eventstartunformatted;
                //$minterval = $page->repeattype->value;
                if ($page->repeattype->value == "D") {
                    $page->of(false);
                    $page->weekdaysevent = '1|2|3|4|5|6|7'; //set the wire array
                } else {
                }
                $weekdays             = $page->weekdaysevent;
                //interval from db
                $startdateunformatted = $page->getUnformatted('date_start'); //interval start
                $startdate            = date("Y-m-d H:i", $startdateunformatted);
                if ($page->intervallimittype == "1") {
                    $enddateunformatted = $page->getUnformatted('recurringdateend'); //interval end
                    $secondstoend       = "86340";
                    $enddate            = $enddateunformatted + $secondstoend;
                } else {
                    $enddate = "7258032000"; //virtual enddate far in the future: 31.12.2199
                }
                $enddate = date("Y-m-d H:i", $enddate);
                $end     = new DateTime($enddate);
                if ($page->repeattype->value == "D") {
                    $step = "1";
                    $unit = "W";
                } else {
                    $step = $page->steps; //Number for the interval
                    $unit = $page->repeattype->value;
                }
                $dow1 = $dow2 = $dow3 = $dow4 = $dow5 = $dow6 = $dow7 = "";
                //get all activated days
                foreach ($weekdays as $number) {
                    ${"dow$number"} = true;
                }
                $daysarray = array(
                    "",
                    "monday",
                    "tuesday",
                    "wednesday",
                    "thursday",
                    "friday",
                    "saturday",
                    "sunday"
                );
                for ($i = 1; $i < 8; $i++) {
                    if (${"dow$i"}) {
                        ${"dow$i"}   = $daysarray[$i]; //get checked days name
                        ${"start$i"} = new DateTime($startdate); //create start dates for every weekday
                        ${"start$i"}->modify(${"dow$i"}); //get the first occurence of each day
                        ${"interval$i"} = new DateInterval("P{$step}{$unit}"); //create intervals for each weekday
                        ${"period$i"}   = new DatePeriod(${"start$i"}, ${"interval$i"}, $end); //create periods for each weekday    
                        foreach (${"period$i"} as $date) {
                            $date          = $date->format('Y-m-d H:i');
                            $starttime     = strtotime($eventstarttime); //add time in seconds to date
                            $date          = (strtotime($date)) + $eventstartseconds;
                            ${"array$i"}[] = $date;
                        }
                    } else {
                        ${"array$i"}[] = "";
                    }
                }
                $periods = array_merge($array1, $array2, $array3, $array4, $array5, $array6, $array7); //merge all arrays into one
                $periods = (array_filter($periods)); //remove empty array parts
                sort($periods); //sort array ascending
                if ($page->intervallimittype == "2") {
                    $periods                = array_slice($periods, 0, $page->repeatcount);
                    //set new eventenddate
                    $lastEl                 = array_pop((array_slice($periods, -1)));
                    $page->recurringdateend = $lastEl;
                    $page->publish_until    = $lastEl;
                }
                $i             = 1;
                $periodsnumber = count($periods);
                // Output all days and dates
                $eventcounter  = 1;
                $bookingtypesp = $page->bookingkind;
                $parent = wire('pages')->get($page->id);
                foreach ($periods as $item) {
                    // create new page
                    $k           = new Page();
                    $k->template = 'single-event'; // set template
                    //$k->parent   = wire('pages')->get($page->id); // set the parent
                    $k->parent   = $parent; // set the parent        
                    // Copy page fields from parent page to newly created child pages
                    $k->date_start = $item;
                    $k->eventpricerepeater->import($parent->eventpricerepeater);
                    $k->publish_until        = $item + $diff;                    
                    $k->eventstatus          = $page->eventstatus;
                    $k->participantmaxnumber = $page->participantmaxnumber;
                    $k->eventmaxstatus       = $page->eventmaxstatus;
                    $k->eventmaxstatus       = "1";
                    //get start date and create path name
                    $eventstartmultiple      = date("Y-m-d", $item); //get date for URL
                    $k->title                = $page->title;
                    //check for other languages
                    foreach ($this->languages as $lang) {
                        $lname   = $lang->isDefault() ? '' : $lang->id;
                        $default = $this->languages->get("default");
                        if ($page->title->getLanguageValue($lang)) {
                            $k->set("name$lname", $page->title->getLanguageValue($lang) . '-' . $eventstartmultiple);
                        } else {
                            $k->set("name$lname", $page->title->getLanguageValue($default) . '-' . $eventstartmultiple);
                        }
                        $k->set("status$lang", 1);
                        $k->save();
                    }
                }
                $this->message("Es wurden " . ($periodsnumber) . " Kopien der Verantstaltung {$k->title} erstellt");
                $page->recurring = 0;
                //END RECURRING
            }


    }

The code at the beginning is only for calculating the start dates of the child pages - so dont be confused!

The creating of the child pages starts at "foreach($periods as $item)"

 

Link to comment
Share on other sites

16 minutes ago, kongondo said:

Is this getting saved anywhere btw?


 $page->recurring = 0;

Yes this will be saved to the parent page. Its only a checkbox that should be checked if the child pages should be created, no need to put it before a page save, because it will be saved from the hook function.

Thanks for your help!!!!!!!!

Link to comment
Share on other sites

Just seeing @adrian answer. That's definitely it. I've run out of time so can't test further. See if Adrian's solution works. If not, hopefully others chime in, meanwhile.

Quick btw: In the code where you delete child pages, no need to delete their repeater pages there as well. They will be automatically deleted when you delete the child pages.

 

  • Like 1
Link to comment
Share on other sites

Found a working solution. If you move the code to delete the child pages to AFTER creation of the new child pages, it works. You can then delete the older children pages. After that though, I hit another snag. Haven't been able to delete the repeater pages of the older children :P

Link to comment
Share on other sites

9 minutes ago, kongondo said:

Found a working solution. If you move the code to delete the child pages to AFTER creation of the new child pages, it works. You can then delete the older children pages. After that though, I hit another snag. Haven't been able to delete the repeater pages of the older children :P

Have you guys tried just changing the parent page of the repeaters? Wouldn't that avoid complications of creating new and deleting old?

That's what I did in that pagetable to repeater converter.

Link to comment
Share on other sites

38 minutes ago, kongondo said:

That would require a different repeater field for the child pages, no?  In addition, from what I can tell, he wants to delete the older children.

Are we talking children of pages or the children that make up the repeater field (that live under the Admin branch). If the latter, then I think my idea sounds good, but again, sorry I really haven't read through this thoroughly. I really am just thinking from the experience I had with that Pagetable to Repeater(matrix) converter. I didn't recreate the child pages, I just moved them from the page table parent to a repeater parent location and then renamed them using the repeater naming scheme. There was lots more to it than just that, but this way it turned out completely reliable, whereas copying resulted in all sorts of problems with there were complex sub-field types involved.

Anyway, I think I should bow out from this now as I really am not following properly :)

Link to comment
Share on other sites

Hello @adrian and @kongondo

the following piece of code still works:

$k->eventpricerepeater->import($page->eventpricerepeater);

What was the problem?

The problem was that I had to delete the parent page after made changes in the hook module. My mistake was that I havent done this, so the repeaters pages were not beeing created properly.

After deleting the parent page and creating a new parent page (event) everything works fine. I dont get any error message and the repeaters will be copied to the children.

Screenshot_3.jpg

The new parent page ist called "ABC Termin" (= ABC event). All repeaters were created properly and all values are inside the repeaters.

Thanks for your help!!!!!!!

Link to comment
Share on other sites

One problem resists. This only works on the first creation of the children. If you create the children again - all the repeater pages of the children will be deleted via $child->delete(); and will not be created new. So

$k->eventpricerepeater->import($page->eventpricerepeater);

will not be working once more.

So it seems that $child->delete is responsible for this behavior. :(

To clearify: The default children of the event (frontend pages) will be created, but not the repeater children. Here I get the duplicate entry error message again.

After first creation of children:

Screenshot_4.jpg

After creating the children once more:

Screenshot_5.jpg

Duplicate entry error message appears and prevents creating of children repeater pages once more. But there are no children pages there - so where is the duplicate entry message coming from.

Error saving field &quot;eventpricerepeater&quot; - SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1482406846-2793-1-0' for key 'name_parent_id'

 

Link to comment
Share on other sites

The duplicate entry went away for me only if I created the new children first, then deleted the older children. Repeater items would be copied over fine to the new child pages. The only issue then was that for some reason, I couldn't delete their leftover repeater pages...

Link to comment
Share on other sites

I have tried to grab the ids of the "old children" and put it into an array with this piece of code before the children start beeing created:

                //grab the old children and make array of their ids.
                $oldchildrenids = array();
                foreach ($page->children('include=all') as $child) {
                      $oldchildrenids[] = $child->id;
                }

This works so far.

To delete the "old children" and their repeaters I have used this piece of code:

                       //delete old children
                        $pages = $this->wire('pages');
                        $repeaterID = $this->wire('fields')->get('eventpricerepeater')->id;
                        // this is the 'repeater_field_name' page
                        $repeaterPage = $pages->get("parent.name=repeaters, name=for-field-$repeaterID");
                        foreach($page->children as $child){
                          if (in_array($child->id, $oldchildrenids, true)) { //grab only the old children
                            $id = $child->id;
                            $child->delete(); //delete all old children
                            $this->message("{$child->id} was deleted");
                            //delete all old repeater pages and their children
                            $childRepeaterPage = $repeaterPage->child("name=for-page-$id");
	                          // will also delete the repeat item pages, e.g. the '1234567890'	
	                          if($childRepeaterPage->id) $pages->delete($childRepeaterPage, true);                          
                          } 
                        } 

Problem: I cannot put the last code snippet after the save() function because then the new children will not be created. Maybe thats a reason because I manipulate the path of the pages with the date as suffix (eventname-21-12-2016). As a result the page itself could not be saved because of the same path.

I need to put it before the save function. The repeater pages were also not created (Duplicate entry error as always)

Link to comment
Share on other sites

Uahh! It was a heavy birth, but now it is working as expected. As @kongondo pointed out, the deletion of the old pages have to take place after creation and saving of the new child pages. But this is only possible if each of the child pages has an unique path name. Otherwise the saving of the new children is not possible (duplication of the path names).

Solution: I have added the current time stamp to the path name. Now the path names of my children look like "name-of-the event-23-12-2016-1482482098". Now every child page has a unique path name.

Now I was able to put the code for deleting the old children after the saving of the new created children.

....
....
                    //check for other languages and create UNIQUE path names
                    foreach ($this->languages as $lang) {
                        $lname   = $lang->isDefault() ? '' : $lang->id;
                        $default = $this->languages->get("default");
                        if ($page->title->getLanguageValue($lang)) {
                            $k->set("name$lname", $page->title->getLanguageValue($lang) . '-' . $eventstartmultiple . '-' . time());
                        } else {
                            $k->set("name$lname", $page->title->getLanguageValue($default) . '-' . $eventstartmultiple . '-' . time());
                        }
                        $k->set("status$lang", 1);
                    }
                    $k->save();
                }
                //delete old children
                $pages        = $this->wire('pages');
                $repeaterID   = $this->wire('fields')->get('eventpricerepeater')->id;
                // this is the 'repeater_field_name' page
                $repeaterPage = $pages->get("parent.name=repeaters, name=for-field-$repeaterID");
                foreach ($page->children as $child) {
                    if (in_array($child->id, $oldchildrenids, true)) {
                        $id = $child->id;
                        $child->delete();
                        $this->message("{$child->id} wurde gefunden und gelöscht");
                        $childRepeaterPage = $repeaterPage->child("name=for-page-$id");
                        // will also delete the repeat item pages, e.g. the '1234567890'	
                        if ($childRepeaterPage->id)
                            $pages->delete($childRepeaterPage, true);
                    }
                }

The deletion code is now after the saving of the new children. Each child gets an unique path name by adding the time stamp via "time()" at the end.

Every time i create new children, they will be created properly (and the corresponding repeaters too). All old children and repeaters will be deleted properly.:)

Special Thanks to @kongondo and to @adrian for their patience and help getting this to work.

  • Like 2
Link to comment
Share on other sites

Brilliant! Your code could be optimised further like so:

$childrenIDs = implode('|', $oldchildrenids);
foreach ($pages->find("id=$childrenIDs") as $child) {
	$child->delete();
	$this->message("{$child->id} wurde gefunden und gelöscht");
	$childRepeaterPage = $repeaterPage->child("name=for-page-{$child->id}");
	// will also delete the repeat item pages, e.g. the '1234567890'	
	if ($childRepeaterPage->id) $pages->delete($childRepeaterPage, true);
}

In the find, we only fetch the children whose IDs we saved earlier, rather than all children first, then checking their IDs.

  • Like 1
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...