Kirby 2.1 Hook Usage- Creating Subpages

So, I briefly tested this in a new Starterkit. Now here some basic steps:

  1. Create a new file in plugins, e.g. hooks.php

  2. Register a hook like this:

    <?php
    kirby()->hook('panel.page.create', function($page) {
    // your hook code
    });
    

    Now, every time any page is created in the panel, the hook is triggered and the code inside is executed.

  3. For test puposes, I created a structure field in the projects blueprint like this

    ```text
    projects:
     label: Projects
     type:  structure
    fields:
      projecttitle:
        label: Project Title
        type: text
     projectyear:
       label: Project Year
       type: text
    
    
    
  4. Next, in the hook.php file I added the following code:

<?php
kirby()->hook('panel.page.create', function($page) {

//check if page is child of projects

if($page->isChildOf(page('projects'))) {
	$newPage = $page;
}
// this is the page which has the structure field type
$projectpage = site()->find('projects');

// get existing entries in an array
$projectarray = $projectpage->projects()->yaml();

// add a new entry to the array
$projectarray[] = ['projecttitle' => $newPage->title(), 'projectyear' => $newPage->year()];

// update the structure field type
page('projects')->update([
   'projects' => yaml::encode($projectarray)
]);

});

So, what this does, every time a page is created, the new page is added to the structure field (code based on a suggestion in this post Add method to append to structure field. Guess, we can now use the new toStructure() method here as well and add to the collection.

Note: This is just to show the basic functionality.

You would have to create a second hook with the same code but panel.page.update instead of panel.page.create to update the field when the page is updated as well.

There are some downsides to this basic code, in the first step (when the page is created) nothing but the title will be added to the structure field. Also, every time you update (with the second hook), a new entry will be created, so the code needs to be refined to prevent that.

But in general, the above works, which is a starting point, I hope …

2 Likes

And here an example of the other way round, i.e. create a new page when the structure field is updated (reference is the blueprint for the projects page in the previous post).

<?php

function seoUrl($string) {
    //Lower case everything
    $string = strtolower($string);
    //Make alphanumeric (removes all other characters)
    $string = preg_replace("/[^a-z0-9_\s-]/", "", $string);
    //Clean up multiple dashes or whitespaces
    $string = preg_replace("/[\s-]+/", " ", $string);
    //Convert whitespaces and underscore to dash
    $string = preg_replace("/[\s_]/", "-", $string);
    return $string;
}

kirby()->hook('panel.page.update', function($page) {

foreach(page('projects')->projects()->yaml() as $projectpage) :
    $title = seoUrl($projectpage['projecttitle']);
    //check if page exists	
    $children = page('projects')->children();
    if(!$children->has($title)) {
	try {

  $newPage = $children->create($title, 'project', array(
    'title' => $projectpage['projecttitle'],
    'year'  => $projectpage['projectyear'],
    
  ));

  echo 'The new page has been created';

} catch(Exception $e) {

  //echo "The page already exists";

 }
}
endforeach;
});

Known problems with this code:

  • the catch exception doesn’t work, therefore I commented it out; otherwise it throws an error message and the page cannot be saved (any solution?)
  • The existing pages are not updated if changes are made to the existing content, so a page update function is also required
  • if the title of the structure field is changed, a new page will be created, which doesn’t make sense, so a solution is needed for this problem as well
  • and also, if a structure field is deleted, the page would have to be deleted as well
  • maybe some more issues that I haven’t considered yet … But as I said, consider it a starting point.
2 Likes

Thanks for posting your findings @texnixe. I’m hoping to play with the hooks a little later today and your examples provide a nice leg up.

Well, its nice to use this opportunity to play around with the hooks, cause that’s really great, however, I’m not convinced, that duplicating content like this is really the best solution to @aftereffectsmagic original problem.

I’d rather change the input that is expected by the plugin or create my own to handle events rather than doing this. To me it seems a bit complicated to find out which structure fields have been deleted vs. just changed, and then delete or update or create new pages.

Thanks for the outline of the two methods, @texnixe!

I decided to try out the first solution as that was provided via a step-by-step solution, but when I save the hook under plugins, every time I access the panel or attempt to create a new page, the text of the hook appears in the header of the panel window and inserts itself in the URL field of the page creation pop-up window.

And when I save an entry to my structure field, nothing happens… the Kirby search icon in the top right corner turns into a spinning icon and it stays like that until I refresh the page.

So just to make sure I have everything right, I’ll show you my step by step process:

  1. Create projects.php blueprint with the same structure field you provided above. There is no blueprint set up for the children / subpages that will be created. Should there be?

  2. Create top-level projects folder titled Projects

  3. Create events folder under Projects titled Events (projects/events)

  4. Save panel.page.create hook as hook.php to plugins folder.

  5. Create new structure field entry in projects/events

  6. Click “save”. The structure field now has the new entry, but nothing else happens.

I want to make sure that this basic function works before including a separate hook for panel.page.update. Thoughts on why this is not working?

My bad, I was lazy and left out the “<?php” bit at the beginning of the file, I’ll add it above …

The first step-by-step procedure is supposed to create new structure entries when a new page is created, so that won’t work with your current setup. If you want to create new pages when you create a new structure field entry, then you have to go for the second method.

I used the Kirby Starterkit as a base, it has a projects folder and some project subfolders and the corresponding blueprints and only added the structure field to the projects.php blueprint.

@aftereffectsmagic:
I have perhaps a stupid question: “Why are you making this so complicated?”
For this I would like to outline another way to create and maintain an event calendar in Kirby.

Some time ago I have developed a Kirby’s website (http://www.sav-barchfeld.de/) with a calendar of events (http://www.sav-barchfeld.de/kalender). There I have chosen in my view an easier way with the need to use the panel by the editors, which is here the default for all pages. But this could certainly be replaced by appropriate input page.

In my understanding, the main difference is something else:

I have created a content type “event” together with the associated blueprint, that contains all the fields, that are required for the representation of an event. In the blueprint of the calendar I have enabled the page type event as a possible child of the calendar.
I had to add the representation of all events, that “visible” children of the events are, in the template of the calendar.

The editor now has the ability in the panel to create a new event, then to fill in the fields and to upload photos or other files as needed or, as usual, to turn any event “visible” or “invisible” or update or change specific individual fields from existing events, like:

(screenshot of an event in the panel / here in English language / !click two times in this picture to grow it here!)

The uploaded files will have to be taken either into rendering in the template, being assigned in the panel possibly by some fields provided or be inserted in a textarea as an image or similar. But that is normal building a Kirby website.
In the above-mentioned website, we have been decided, if at all, to integrate pictures on the field Text of the event (using kirbytext).

Note: My way to store this data is commonly known as the relational model of data (https://en.wikipedia.org/wiki/Relational_database).

Hint: If you want you can protect the events from being viewed at an own webpage.

Good luck!

[last edited: 2015-05-22 16:05 / German time]

1 Like

Ha, removing the text from the header section of the panel was simple. I should’ve thought to check to make sure the php bit was there. Thanks, @texnixe. That’s part of the problem solved! Testing the rest now.

Could you post the blueprint for this screenshot? I really couldn’t understand what you’ve meant.

Thanks

I don’t quite see what these events have in common with a relational database? We are talking files here not databases.

1 Like

@texnixe:
The answer to your questions depends on the approach. The data in the Kirby directory “/content” is, if one looks at the information theory, a data structure that is denoted by “relational model of data”. In my possibly incomplete knowledge of this subject many studies have been published on the subject “relational database”. Therefore I have taken the liberty for readers, who want to deepen their knowledge of this subject, to provide a link for “relational database” at Wikipedia. The second link in this Wikipedia article leads to the page “relational model of data”.

For the purpose of this method it once does not matter what kind of storage medium (e.g. files or databases) we are dealing here, as long as the direct access (sometimes called “random access”) is ensured to the data. If the storage medium clumsy selected (this does not apply to Kirby), you can apply this system though, but may have to contend with runtime problems.

Furthermore the linked Wikipedia article is an excellent introduction to this topic in more extensive data collections, which I can recommend (not just for websites) all very novice in the design of data storage. All I can recommend is a profound knowledge of the normalization of a database (see link below at the linked Wikipedia page).
To make these considerations is with me by the way the first step to begin the development of a bigger new website.

In order not to be misunderstood: I as an engineer have this now shown here only so extensively to answer your questions as completely as possible. This is not intended to be a affront of the importance of design or designers and please should not be understood wrong. Thank you!
I therefore hope that this information theory oriented part of the topic is now completed. We should be devoted to the actual topic of this page again.

Now I have translated the blueprints using Google to English.

The blueprint “events.php” (for the calendar) looks like:


<?php if(!defined('KIRBY')) exit ?>

title: Calendar of events
pages:
  sortable: true
  num: date
  field: date
  template:
    - event
files: true
deletable: false
fields:
  title:
    label: Title
    type:  text
    help:  Please enter the "name" of this page, is also used in the navigation (menu etc.).
  text1:
    label: Starting text
    type:  textarea
    help:  Enter the "starting text" of the page. The starting text will be added automatically under the title and above the list of events.
  catchstatistics:
    label: Submission of catch statistics
    type: date
    width: 1/2
    default: 31.12.2015
    format: DD.MM.YYYY
    required: true
    help:  Please select the "Submission of catch statistics".
  membershipfeepayment:
    label: Date of the membership fee payment
    type: date
    width: 1/2
    default: 31.03.2015
    format: DD.MM.YYYY
    required: true
    help:  Please select the "Date of the membership fee payment".
  text:
    label: Final text
    type:  textarea
    help:  Enter the "final text" of the page. The final text will be added automatically under the list of events.

The blueprint “event.php” (my screenshot) looks like:


<?php if(!defined('KIRBY')) exit ?>

title: Event
pages: false
files: true
fields:
  title:
    label: Event
    type: title
    help:  Please enter the "name" of this event, is also used in the navigation (menu etc.).
  categories:
    label: Typ of event
    type: radio
    default: 6
    options:
      1: Official events
      2: Fishing events
      3: Meetings
      4: Work assignments
      5: Events of the association
      6: Others
  date:
    label: Date (beginning)
    type: date
    width: 1/2
    default: today
    format: DD.MM.YYYY
    required: true
    help:  Please enter the "beginning" of the event.
  time:
    label: Time (beginning)
    type: time
    interval: 15
    width: 1/2
    help:  Please enter the "beginning" of the event (Enter "00:00" for full-time events)!
  dateto:
    label: Date (end)
    type: date
    width: 1/2
    format: DD.MM.YYYY
    help:  Please enter the "end" of the event (Select only if it differs from the beginning).
  location:
    label: Meeting point
    type: text
    help:  Please enter the "meeting point" of the event.
  text:
    label: Text
    type: textarea
    help:  Please enter the "description" of the event.
    

Good luck!

1 Like

Thank you @HeinerEF , much appreciated.

I was doing something similar and got curious about how you’d tackle this.

This seem to be the only good example of how to make a hook that creates a new page based on the adding or editing of a field in another page.

Is there any other documentation on this? Or on how to use hooks?

In my case I am thinking of using a hook to create a new page (in an adjacent folder to the one I am in) whenever a new tag is added to a tag-field, and also when it is modified etc.

Going through @texnixe example code:

  1. you are first checking if the page with that title has been already created
  2. if not, you are creating a new one

Where do you set which panel page triggers the hook, and where the new created page is placed?

In general, the hook is always triggered, but within your hook, you can of course limit the pages the hook is used on.

To create a page as a sibling of the current page, use $page->siblings()

I tried to re-work out your example to fit what I need to do, which is:

whenever I add a tag (in a field called technique) to a subpage of /artworks, create or update a subpage in /techniques accordingly.

What I have so far is this in a plugin file:

<?php

$artworks = $site->grandChildren('template', 'artworks')->children();
$tags = $artworks->pluck('technique', ',', true);

if(!empty(param('technique'))) {
  $artworks = $artworks->filter(function($result) use($tags) {
    // split tags and convert to lowercase
    $tags = array_map('str::slug', $result->technique()->split(','));
    // return the page if the filter is in the array
    if(in_array(str::slug(param('technique')), $tags)) return true;
  });
}

kirby()->hook(['panel.page.create', 'panel.page.update'], function($page) {
  foreach($tags as $tag) :
    //check if page exists
    $techniques = $site->grandChildren('template', 'techniques')->children();
    if(!$techniques->has($tag)) {
	try {

  $newPage = $techniques->create($tag, 'technique', array(
    'title' => $tag()
  ));

  echo 'The new page has been created';

} catch(Exception $e) {

  //echo "The page already exists";

 }
}
endforeach;
});

The two variables at the top give me an error saying they are undefined—I am using the same two variables in a template page and have no problem.

$site does not work in a hook, you need to define it first: https://getkirby.com/docs/developer-guide/advanced/hooks/#accessing-panel-kirby-site-and-user-in-a-hook-callback

I think I am making this all too complicated.

What I am trying to do is to replace how kirby’s tag filter works, eg kirby.site/tags:value to kirby.site/tags/value.

I tried to do this first with a route, by modifying the url—but did not manage to reach any good result. Then I thought that by doing a container page gathering as subpages all tags used in another set of subpages would do the trick, but it seems I am working against kirby’s own grain.

The reason why I am pushing for this is that I’d like to have as final urls

kirby.site/collection/authors/name
kirby.site/collection/artworks/name
kirby.site/collection/tags/name

Do you think is there any way to accomplish this in a way that the final user managing the website does not have to entry multiple times the same informations?

Is using a set of hooks viable in this case?

No, in fact, I think that a route is the right way to achieve this.

1 Like

Thanks for confirming it!

Will go back to this approach then. Was getting totally lost in possible other approaches.