Kirby Blueprint Logic

From time to time there are suggestions about having logic in the blueprints, but for security concerns (?), blueprints are static yml files.

It’s not that easy to make a plugin to use logic in blueprint files because they are not very pluggable. However I’ve come up with an idea that works.

Plugin

Installation

  1. Save the plugin code (down this page) as a file and place it in plugins/kirby-blueprint-logic/kirby-blueprint-logic.php.
  2. Done!

Setup

Add a php file and place it in blueprints/_project.php if your blueprint filename is project.yml. The underscore is important.

Usage

In the PHP, add stuff just like a blueprint, but you can use logic as well.

title: Project
fields:
<?php
for( $i = 0; $i < 3; $i++ ) : ?>
  my_field_<?php echo $i . "\n"; ?>
    label: My field <?php echo $i . "\n"; ?>
    type: text
<?php endfor;

Pitfalls

  • In some odd cases like if you rename a page to edit or template. It can trigger errors.

The plugin code

<?php
if( class_exists('panel') ) {

class BlueprintLogic {
	public $path;
	public $url;
	public $panelpages;
	public $id;
	public $page;
	public $yml;
	public $ymlFile;
	public $snippet;

	public function setup() {
		$this->setPath();
	}

	public function setPath() {
		$this->panelpages = panel()->urls()->index() . '/pages/';
		$this->setCurrentUrl();

		if( $this->isPage() ) {
			$path = kirby()->roots()->blueprints() . DS . 'default.yml';
			$this->setId();
			$this->setPage();
			$this->setYml();
			$this->setPhp();
			if( ! empty( $this->php ) ) {
				$this->loadPhp();
			}
		}
	}

	public function setCurrentUrl() {
		$this->url = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
	}

	public function setId() {
		$url = $this->url;

		$url = preg_replace('/\/edit$/', '', $url);
		$url = preg_replace('/\/toggle$/', '', $url);
		$url = preg_replace('/\/template$/', '', $url);
		$url = preg_replace('/\/url$/', '', $url);
		$url = preg_replace('/\/delete$/', '', $url);
		$url = ( strstr($url, '/file/', true) ) ? strstr($url, '/file/', true) : $url;
		
		$url = str_replace($this->panelpages, '', $url);

		$this->id = $url;
	}

	public function setPage() {
		$this->page = page($this->id);
	}

	public function setYml() {
		$root = kirby()->roots()->blueprints();
		if( file_exists( $root . DS . $this->page->template() . '.yml' ) ) {
			$this->yml = $this->page->template();
		} else {
			$this->yml = 'default';
		}
		$this->ymlFile = $root . DS . $this->yml . '.yml';
	}

	public function isPage() {
		if( str::contains( $this->url, $this->panelpages ) ) {
			return true;
		} else {
			return false;
		}
	}

	public function setPhp() {
		$php = kirby()->roots()->blueprints() . DS . '_' . $this->yml . '.php';
		if( file_exists( $php)) {
			$this->php = $php;
		}
	}

	public function loadPhp() {
		$php_content = tpl::load($this->php, array(
			'site' => site(),
			'children' => site()->children(),
			'page' => $this->page,
		));

		$yml_content = tpl::load($this->ymlFile);

		if( $php_content != $yml_content ) {
			f::write($this->ymlFile, $php_content);
			go($this->url);
		}
	}
}

$blueprintlogic = new BlueprintLogic();
$blueprintlogic->setup();
}

I will release it on github in 2017.

What do you think of the idea?

Interesting. A couple of months ago I looked into that, but I basically gave up after a couple of hours once I realized how “hacky” the solution would get. Turns out, I was right ;).

No offense meant @jenstornell, I am completely on board with the concept and I like knowing what a working implementation would look like and I also think you should make this available on Github… but, that doesn’t mean that I am going to use it.

1 Like

You are right, this feature require a hacky solution. :slight_smile: If this would have been implemented into the core, it would have been solved in another way.

What this plugin does is kind of like caching the logic files into yml files when they change.

1 Like

To be honest, I think that generating code with code is pretty :scream:. Especially with languages that care about indentation like YAML.

2 Likes

I love the notion of dynamic programming generally speaking. With languages like Ruby, where the whole system is based on the notion of code “unfolding” into better code, this can be quite amazing and reduce potential error sources drastically. Not with PHP though.

But this is not about generating code. What @jenstornell is actually doing is generating configuration - with the sole purpose of reducing duplication, making it more DRY. I think that the concept is very useful for building medium to large websites where, almost by definition, you end up with a lot of similarities between blueprints. I just find adding a field to 20 different blueprints a bit tedious if I could do it with 1 modification instead.

Personally I’d :heart: the option of building blueprints dynamically with code within Kirby itself. Adding a common set of fields to a blueprint with one line; having the option to tweak the blueprint structure slightly depending on the context; generally speaking, building blueprints out of smaller blue(r)prints… Sounds like fun to me.

2 Likes

Yes, he is generating configuration. But he is also generating code as YAML is going to be parsed again.

Don’t get me wrong: I love dynamic configuration, it can be very useful. But please don’t try to generate YAML with PHP “by hand”.

Here’s an idea how this could be solved instead: Return an array from the PHP blueprint and build clean YAML from that in the plugin (yaml::encode()) to pass that to Kirby.

3 Likes

Agree 100%. I did not catch on to that on my first read-through of the code.

But the fact that this is generating a PHP array to build a YAML that is the parsed to a PHP array remains quite sketchy to begin with. I was even thinking about running my own Kirby fork, just to add that feature to the core, but after reading some of the discussions around this I came to the conclusion that I’ll just accept that architecture decision in the spirit of KISS.

1 Like

Here’s an idea how this could be solved instead: Return an array from the PHP blueprint and build clean YAML from that in the plugin (yaml::encode()) to pass that to Kirby.

Do you mean by my plugin or by the core? If it’s in a plugin it’s many steps before the output:

  1. PHP code
  2. PHP array
  3. Encode to Yaml
  4. Decode from Yaml to array - by the core from the blueprint.yml

Possible solution

But I’ve got an idea for the core. If this was what you ment, I will take no credit for it. :slight_smile:

Blueprint file

project.php - a blueprint file.

Blueprint content

return array(
);

So it basically just returns an array from the php-version of the blueprint.

Benefits:

  • Because it’s just an array it should be secure.
  • Because it’s an array, there is no need to yaml decode it as it’s already an array.
  • Because it’s returned, it’s possible like a controller to populate it by logic (loops etc).

Steps like above would be:

  1. PHP array

From 4 steps to only 1 step.

Any pitfalls?

1 Like

By the plugin. If this was a core feature, we wouldn’t need the YAML encoding and decoding of course. :slight_smile:

What you proposed for the core is exactly what I thought as well, we would just need to make it backwards-compatible with existing YAML blueprints with the .php extension.

3 Likes

That sounds great! I added an issue about it: https://github.com/getkirby/panel/issues/979 :slight_smile:

1 Like

The field cannot be extended “<?php”

This is the error I’m getting any idea why this might be?

I did all the steps above. Plugin, _home.php file.

title: Home
pages: true
files: true
options:
 template: true

fields:
 <?php
 for( $i = 0; $i < 3; $i++ ) : ?>
  my_field_<?php echo $i . "\n"; ?>
     label: My field <?php echo $i . "\n"; ?>
     type: text
 <?php endfor;

This solved it for me. Dynamic Yaml File Fields from a PHP snippet, etc. I hope this helps someone generate dynamic fields in yaml, you can use php, webservices or whatever your heart desires.

if you drop this line

	public function options() {
		return call_user_func($this->options, $this);
	}

in multiselect

Then you can have as many fields and they become searchable.

My plugin was made in a kind of desperation and it’s a really hacky approach. I do not maintain it anymore and for me personally, I’ll just wait until they figure something out in a future version of Kirby. Sorry about not being able to help out.