Jump to content

Parse tags / php inside content


piranha
 Share

Recommended Posts

I usually find that I need to put tags or php tags inside the content itself e.g. inside the body. For example if I want to show a list of pages in just a particular page. I don't want to do this in the template because is a one of thing. PW doesn't seem to be able to do this. Or does it?

So I think it will be a great feature if the PW will be able to parse tags/php inside page fields.

Link to comment
Share on other sites

Hi Piranha and welcome to the forums :)

First things first: You're doing it wrong.

You, sir, are thinking in terms of classic CMS, where page = one and only template. However, it's very easy to just create another template, call it "my-content-with-list" and put something like this into it:

  [include header]
    <div id="content">
      echo $page->body;

      <div id="list-o-pages">
      [php code for listing pages]
      </div>
    </div>
   [include footer]

very simple. :)

If you have any other questions 'how I do this', that documentation or faq section hasn't answered yet, please post it into API & templates or faq section, describe what you need and we'll help you find the right solution.

Adam

Link to comment
Share on other sites

Adam is right that you really are better off doing this in templates. But if you still have a specific need to do this, here's how you can. I would suggest setting up a field specifically for PHP code, and call it "phpcode" or something like that. Make it a standard textarea field, and disable any output formatting in the field's settings.

In your template code, you will want to eval() the field's value, which will evaluate it as PHP. This is how other CMSs do it. It's not particularly safe or efficient, so be careful where you use it.

if($page->phpcode) eval($page->phpcode); 

The reason I suggest using a field just for PHP code is that in a regular textarea field, you would want to have output formatting like entity encoding. You don't want that in a field that supports PHP, because it would break the PHP. So mixing bodycopy and PHP in the same field would not be a best practice (though you could certainly do it).

Link to comment
Share on other sites

Thanks for the responses. I think that PW is a great CMS, it has a lot of good ideas. But I disagree on this. I don't think that creating a template for each specific need is a good solution.

Let's say for example that a client want to have a page with some text at the top, then a dynamic list of pages and then some text on the bottom. If you could put some php code or tags directly on the body this could be easily achieved. But in PW this is not possible. I will have to create an specific template just for this page. At the end you end with lots of specific templates.

I hope you see my point.

Thanks

Link to comment
Share on other sites

Then by all means, create your custom templating language.

I don't know how many these custom PHP codes you needs in your templates, so this might not be the best way, but if you need just a few, you can do:

IN body, just add something like {{submenu}} into place where submenu should be generated

in your template

  foreach($custom_tags as $tag){
    str_replace('{{'.$tag.'}}', custom_tag_code($tag), $page->body);
  }

I know. Not the most clean code, but currently probably the best solution if you don't want to have dozens of custom templates :)

Note: Some people actually really dozens of templates, and the system works just fine. so 1. it's subjective or 2. it really might be the way to work with this CMS.

Link to comment
Share on other sites

Piranha,

I'm not suggesting that you should create lots and lots of templates. What I am suggesting is that your templates are PHP files, not static files, so you are not bound to the limits of static files. On my sites, I typically have only one markup template, and use the other templates as flow control in preparing whatever is needed for the main template. But you should use templates in whatever way works best for you.

The need you mentioned with using a tag within the content of an existing field is one I can relate to. The need does come up occasionally. Though I don't like my clients having to use tags, so I usually design in a way that maintains separation between their content and that which is generated at render time. But that may not be right for you, so I'm going to take Adam's suggestion and turn it into a module. This took all of 10 minutes to create, but it should give you a good starting point to build from. Though it is fully functional as-is.

Place the attached module file in /site/modules/TagscriptSimple.module. Then go to Admin > Modules and click the "check for new modules" button at the bottom. Then locate the TagscriptSimple module, and click "install" on that screen.

Now in any of your page fields (or templates) you can type a {{tag}} which can either be a page property or a function name. For example {{title}} would echo the current page's title. Or {{myfunction}} would call a function named tagscript_myfunction, and it would pass it the current page. It would then replace the {{myfunction}} tag with whatever output was returned by the function. I included a couple of examples in the module code if you want to try them, so try {{hello}} as well as {{children}} to see the examples run.

Here is the module code. I've also attached it in a ZIP file ready do download, so that you don't need to copy/paste. This is fairly basic. Anyone please feel free to take this further, and please post the update. An example of how you want to take it further would be to translate some tags to config URLs, or to include the ability for tags to pass arguments. Though if nobody takes this up, I'll do it here.

<?php
/**
* Simple tagscript parser to serve as an example or starting point.
*
* Looks for tags surrounded by double curly brackets, like {{example}}.
* When it finds one, it first checks if there is a funcing matching the 
* name: tagscript_example(). If it finds a matching function, then it 
* calls it, passing it the current $page object (in case it needs it). 
* The function should return whatever should replace the tag. 
*
* If the matching tag doesn't resolve to a function, then it next checks
* if the tag lines up with a field from the current page. If so, the 
* tag is replaced with the field's value from the page. 
*
*/

class TagscriptSimple extends WireData implements Module {

       public static function getModuleInfo() {
               return array(
                       'title' => 'Tagscript Simple',
                       'summary' => 'Translates {{tags}} to PHP function calls.',
                       'version' => 100,
                       'permanent' => false,
                       'autoload' => true,
                       'singular' => true,
                       );
       }

       public function init() {
               $this->addHookAfter('Page::render', $this, 'render');
       }

       public function render(HookEvent $event) {

               $page = $event->object;
               $out = $event->return;

               // don't process tags in the admin
               if($page->template == 'admin') return;

               // quickly determine if there are no parsable tags here
               if(strpos($out, '{{') === false) return;

               // match all tags enclosed by double brackets, like {{tag}}
               if(!preg_match_all('/\{\{([_a-z0-9]+)\}\}/i', $out, $matches)) return;

               // iterate through all found tags
               foreach($matches[1] as $key => $value) {

                       // first determine if there is a function with the same name as the tag
                       // except preceded by "tagscript_", i.e. "tagscript_functionName"
                       $functionName = "tagscript_$value";

                       // if the function exists, then call it, passing it the $page object 
                       if(function_exists($functionName)) {
                               $result = call_user_func_array($functionName, array($page));
                               if($result !== false) $out = str_replace($matches[0][$key], $result, $out);

                       // if function doesn't exist, check if it's a page property
                       } else if(($result = $page->get($value)) !== null) {
                               $out = str_replace($matches[0][$key], $result, $out);

                       // if none of the above, then leave it alone
                       } else {
                               // do nothing, or expand from here...
                       }

               }

               $event->return = $out;
       }
}

Here are a couple of example functions I also put in to test. These are just regular functions (not class functions).

<?php
/**
* Function to test the tagscript parser
*
*/
function tagscript_hello($page) {
       return "<p>Hello World. The current page path is {$page->path}</p>";
}

/**
* Render a list of the page's children
*
*/
function tagscript_children($page) {
       if(!$page->numChildren) return '';
       $out = "<ul class='children'>";
       foreach($page->children() as $child) {
               $out .= "<li><a href='{$child->url}'>{$child->title}</a></li>";
       }
       $out .= "</ul>";
       return $out;
}

TagscriptSimple-module.zip

Link to comment
Share on other sites

  • 1 month later...

Wow, this is really cool!

Now this is something I've been giving a lot of thought recently, because I'm rewriting a custom fork of another script: There's content templates and then there's site templates (i.e. site themes).  I must admit this was something I hadn't given enough thought in the past.

To further explain this: As a designer, I often have found sites that require the typical CMS structure (i.e. a big news corp site is almost always the same, lots of blocks, lots of block positions, etc) which means that I need to determine what goes where and, upon determining where things would go, I end up with a basic wireframe or content skeleton.  This structural form will be the basis for the rest of my implementation (and sometimes is the point of departure to determine which CMS I'm going to be using).

So this is a site theme to me.  This also goes to explain why I proposed the creation of a blank theme, that is, a site structure that can easily be expanded, changed (blocks trading places, etc) and beautified (strictly using CSS, 1 for the structural layout and 1 for the graphical adornments).

But then, inside such blocks, you have your lists.  All websites are, in essence, lists of content.  Lets get back to the news site example and define that we'll have some news reports with video, some with audio, some with pictures and some will be just text (maybe editorials), we'll have sections, tags, topics, as well as the other more obvious fields (date published, author, etc).

On our example's structural layout (theme) we have defined that there will be a lot of blocks, and each and everyone of them will be different in a way, some contain pictures and titles, some just a title linked to the full news, some might have an excerpt to the news piece. We might also add a block with tags, topics, a navigation menu with sections and a list of writers with links to all their news posts.

Now, the way I see it, this would be level two.

In level one, my theme would look something like this:

<html>
<head>...</head>
<body>
<div id="head">{b_header}</div>
<div id="slider">{b_news_slider}</div><div id="tags">{b_trending_tags}</div>
<div id="rnews">{b_recent_news}</div><div="rpics">{b_recent_pics}</div>
<div id="rvids">{b_recent_video}</div>
<div id="alist">{b_author_list}</div><div id="rauds">{b_recent_audio}</div><div id="topics">{b_topics}</div>
<div id="foot">{b_footer}</div>
</body>
</html>

Giving the site's a chance to define a different template to be used by the home screen, section screen, videoplayer screen, single post, etc and also a way to easily customize what goes where, in case the news corp head honchos decide to change the layout, keeping them away from the, more important, level two templates.

Level two templates are how we present stuff inside the structural blocks, don't think of them as merely "here, list 5 items" code (this might very well be defined in the level 1 structural templates) but as a way to create the appropriate variations for each one of them so that we can use and reuse them easily.

While in the home screen I might present a bunch of blocks, I might need that they are less, or ordered differently, in the section part of the site, or that they have more or less content on them.

We might have PW create automatically some of them (single items, full lists, simple lists with just title and link, etc) but we might also have a way for developers to easily create new ones (in videos: preview pic, title with link; in audio: a list with the title and the html tags needed to display an audio player, etc)

Another thing we could gain from this is the ability to create custom xml, xslt, json, rss, and other types of pages.  The level 1 template would take care of the of the surrounding tags and the custom level 2 template would take care of what goes inside, allowing us to create, for example, RSS lists for all items on our site, or just for the news entries; going even further, this could allow us to also create an xml in the specific format that some video players require.

BUT WAIT, you might say this is easily done with what we have already, right? And I can see your point.  Indeed this is achievable right now, but (there's always a but or two) you are viewing it from the developer's perspective.  The news site wants developers making the script more secure and have more features. :) So it will be up to the designers and website admins to keep making changes to the layout. At least that's been my experience.

In any case, this is just my thought on where PW might want to be moving, keeping things simpler to designers (all blocks and layouts can be made with almost any wysiwyg editor) while keeping things flexible and powerful for the developer to come out with new ideas for their clients, and with HTML5 fast approaching, that might be a good thing :P

The request for all those modules (google maps, video playlists, content sliders, RSS, etc) might be solved by something like this, as any new module might be defined as a new input template paired with various output level two templates (blocks) to be included wherever we want inside a level one (structural) output template

So this is my suggestion, we now might be able to easily develop level one templates but (and correct me if I'm wrong) it's not as easy to develop level two templates.  I think this code is a step in the right direction and would value your thoughts on the case I present.

Thanks guys, take care :D

Link to comment
Share on other sites

OK then a shorter version:

Defining current templates as level one templates (structural), I suggest we make it easier to create level two templates (blocks) as this could make things easier to develop customizations for PW.  By doing this we would be able to create RSS templates, Google Maps would be solved by creating a block with the tags needed to render it and taking the data from a "Page" (as we currently call them), featured content sliders, json responses, audio playlists, etc.

By using tags, non-php-savvy developers could generate solutions to everyday tasks easily.  You are a php developer, organizations would need you keeping the code safe, secure, up do date, etc, and they would task designers and/or website admins (people who aren't always knowledgeable in php) to create level one templates (the outside structure) and level two templates (blocks such as news lists in several formats).

Ryan's code is, IMHO, a step in the right direction.

Thanks, take care

Link to comment
Share on other sites

Thanks for taking the time to write this up. I think what you are describing is very similar to what I'd like tagscript to progress to in terms of capability, so I appreciate having this written in more detail. My thought is that you will be able to define a new {tag} either with a function (like in that module above), with a separate module, or by pasting the tagscript snippet into an admin field (which doesn't yet exist, but will be built as a module). If you have the tagscript module installed, it will always translate tagscripts in you templates when you enable it. You'll also be able to selectively turn it on for any custom fields (like if you wanted to enter tags in your bodycopy or something). Being able to turn it on in custom fields is one area where I think tags bring something very useful to the table... you wouldn't ever want to enable PHP in a field, whereas I'd have a much higher comfort level (security wise) with enabling tags in a field.

In terms of syntax, let me know what you think of this? Tagscripts can be simple like:

{tag}

Tags can have arguments, like:

{tag, name="value", name="value"}

Or tags that translate directly to an API function, and be "open", allowing you to create the markup (the closed version would generate it's own markup):

{pages.find parent=/news/, featured=1}
    <li><a href="{url}">{title}</a> {date}</li>
{/pages.find}

That would be the equivalent of:

<?php
foreach($pages->find("parent=/news/, featured=1") as $item) {
    echo "<li><a href='{$page->url}'>{$page->title}</a> {$page->date}</li>";
}

I like the open-tag approach, where you can define your own markup. I'm less comfortable with tags that output markup on their own (level 2 templates, as you described them), but also recognize that not everyone wants to recode the markup for everything, all the time. That's why the pagination and comments modules generate markup... because I don't want to have to code that markup for every site. :) So that's an admission of value for tags that output markup, but I think you always have to be careful with anything that's generating markup you didn't write. So my though is that for any given tag, you can write a closed version like {tag} that generates it's own output, or you can write an open version (like in the example above) or {tag} ... {/tag} where it uses what's in between for output, allowing you to substitute additional tags to obtain the raw values of fields within the {tag} ... {/tag}. I believe this is similar to the approach that Expression Engine tags with it's tags.

BUT WAIT, you might say this is easily done with what we have already, right? And I can see your point.  Indeed this is achievable right now, but (there's always a but or two) you are viewing it from the developer's perspective.  The news site wants developers making the script more secure and have more features.  So it will be up to the designers and website admins to keep making changes to the layout. At least that's been my experience.

When working on a site that involves a team of people responsible for design/development, you are absolutely right about this. For most of the sites I work on lately, it's just me building it and then maintaining it over time. But that's a unique situation, and not how it works half of the time in other work environments. I would agree that tags would be preferable with the site you are describing. While I personally find native PHP solutions simpler and easier to maintain for my own use, I would be inclined to use tags in situations where I knew other designers/developers were going to be making edits too.

I plan to keep ProcessWire's core always PHP API based, but am very enthusiastic about building/collaborating on a module that can make the API fully accessible to tags, and let users define their own tags/snippets in a format that is easy to do and easy to share among sites. I think this will broaden ProcessWire's appeal with one of it's main target audiences (designers), though you said it better than me.

Thanks,

Ryan

Link to comment
Share on other sites

Oh the tags look pretty in your proposal, the commas are there for easier parsing, right?

The thing about having two template levels is an idea about modularization.  By defining where blocks would be separatedly from what blocks might contain we would be able to quickly change a blocks content without needing to change the whole template.

BTW, there is a way to keep things tidy and PHP even if we implement the tags module, which is to make the tag module just an easy interpreter that can translate my tags into PW proved and approved code. So this:

{pages.find parent=/news/, featured=1}
    <li><a href="{url}">{title}</a> {date}</li>
{/pages.find}

would become a file like this:

<?php
foreach($pages->find("parent=/news/, featured=1") as $item) {
    echo "<li><a href='{$page->url}'>{$page->title}</a> {$page->date}</li>";
}

This way the module would not need to be present in a live website and still be the same PW that developers love and enjoy using :)

While "compiling" the parser would call each block, interpret it, and generate the PHP code for it, once you are done, you simply take your site -sans the tagscript module- and upload it and it works :)

Having 2 levels in templates might also mean an easy way to develop custom output templates like RSS, xml lists, json responses etc.  What it would mean though is to have 4 (a header, a list, a footer and a full) templates and a way to call them. Think about tinysong and it's API. The searchbox calls a list of songs that match a query.  By adding a parameter, you get a different response in a different format (namely JSON). And that's just an example, we could list oh so many others in oh so many other formats... In fact, this could mean that PW would be ready to output in just about any format.

Link to comment
Share on other sites

Oh the tags look pretty in your proposal, the commas are there for easier parsing, right?

Yes, the tags would be parsed in the same manner as ProcessWire selectors, so there would be that consistency in the API access.

The thing about having two template levels is an idea about modularization.  By defining where blocks would be separatedly from what blocks might contain we would be able to quickly change a blocks content without needing to change the whole template.

This is the recommended approach now, though with includes and functions. If I'm understanding you correctly, tags used as definable snippets would make that approach accessible from the tag level, which sounds great. But I see this as always being user defined as to how they want to modularize (perhaps in defining their own tags or templates), because ProcessWire intentionally not get involved in any output generation except in rare cases (like with pagination). For instance, the term "blocks" as it's used in Drupal is an approach I want to avoid because it gets the CMS mixed up into the structure of the markup. It's not necessarily a bad approach -- there are positives and negatives, depending on the intended use. But ProcessWire's concept is the opposite of Drupal's in this regard. However, I think you are using that term in a different way, just to refer to user defined blocks or areas within their template, so that's the way I read it and am agreeable to it.

BTW, there is a way to keep things tidy and PHP even if we implement the tags module, which is to make the tag module just an easy interpreter that can translate my tags into PW proved and approved code. So this:

{pages.find parent=/news/, featured=1}
    <li><a href="{url}">{title}</a> {date}</li>
{/pages.find}

would become a file like this:

<?php
foreach($pages->find("parent=/news/, featured=1") as $item) {
    echo "<li><a href='{$page->url}'>{$page->title}</a> {$page->date}</li>";
}

This way the module would not need to be present in a live website and still be the same PW that developers love and enjoy using :)

If I'm understanding you correctly, this is about what I had in mind too. But I wasn't planning to have it convert (compile) to PHP code directly, just because the result would have to be eval'd, which isn't an ideal way to execute PHP, unless there aren't any other options. Instead it would identify the function in the tag ($pages->find() in this case) and send everything after it directly to the selector argument of the function. The result would be cycled through the given markup/tags. But I'm going to make note of this for when I get closer to it, as I want to have as many ideas in front of me when implementing as possible. What I think before implementation and during implementation are often very different things. :)

While "compiling" the parser would call each block, interpret it, and generate the PHP code for it, once you are done, you simply take your site -sans the tagscript module- and upload it and it works :)

I like the creativity in the idea here. My two concerns would be conflicts with regular use of the API, and how a user would maintain this. But I'll assume there is a solution for the maintenance question. Templates are actually PHP include files rather than files that are eval'd separately (like in some other CMSs). Templates are likely to include other templates. A user can assume in their template file that it is a blank slate for building a PHP application. ProcessWire can be used as a PHP framework with lots of functions provided to it. Or you can even pretend that ProcessWire isn't there in your template code... using it like a blank PHP script that has it's own URL and some variables provided to it. But this flexibility necessitates that we don't make assumptions about what they are doing in the code. If we have alternate compiled versions of templates, suddenly PHP vars like __FILE__ would fail and relative includes would fail, among other things. Still, I appreciate the idea and will keep thinking about this to see where it might be applicable in the future. For instance, maybe a module could provide this capability and people would use it with the understanding some things may be a little different from the PHP side.

Having 2 levels in templates might also mean an easy way to develop custom output templates like RSS, xml lists, json responses etc.  What it would mean though is to have 4 (a header, a list, a footer and a full) templates and a way to call them. Think about tinysong and it's API. The searchbox calls a list of songs that match a query.  By adding a parameter, you get a different response in a different format (namely JSON). And that's just an example, we could list oh so many others in oh so many other formats... In fact, this could mean that PW would be ready to output in just about any format.

I like this thinking! But I should note that PW is already ready to output any format. It doesn't get involved in output generation. It has no idea that it's used to output HTML most of the time. :) It provides the data to your template, and your template decides how to output it. So having output of RSS/XML, JSON, etc. at the core level goes against the concept of the CMS. But having that in modules or user-defined templates is consistent with the concept of the CMS. What I believe you are describing are modules (perhaps called as tags) that output in different formats, and that's always good. But I'm not totally sure I'm reading you right, so I'll just put in a short snippet of how we would currently make a page template that outputs a list of it's children in either HTML or RSS (determined by a GET var). Then maybe you can post an equivalent tagged example, if available.

<?php

$children = $page->children(); 

if($input->get->rss) {
    $rss = $modules->get("MarkupRSS"); 
    $rss->title = "Children of " . $page->title; 
    $rss->render($children); 

} else {
    include("./head.inc"); 
    echo $children->render(); // or foreach for your own output
    include("./foot.inc"); 
}

Thanks,

Ryan

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