Change the PHP CLI version

Sometimes on the terminal, the PHP version is different than the PHP version used by the web server.

You can check the PHP version running in the CLI by running the command php -v.
If the PHP version is less than 5.5.9, Grav won't run as it requires at least PHP 5.5.9.

How to fix?

You need to enter some configuration to .bashrc, or to .bash_profile in your user home folder. Create those files if you don't already have them in the user folder. They are hidden files, so you might have to do ls -al to show them. Once the configuration is added, you'll need to start a new terminal session for those settings to apply.

The exact path of course depends on how your system is set up, where it stores the more recent PHP version binaries. That might be something you find in the Hosting documentation, or you can ask your hosting setup if you do not find it anywhere.

You could also try looking in the php-something files or folders under the /usr/local/bin or /usr/local/lib folders, with ls -la /usr/local/lib/ |grep -i php.

Creating a simple gallery

Problem:

A common web design requirement is to have a gallery of some kind rendered on a page. This could be to display photographs of your new family pet, a portfolio of previous design work, or even a basic catalog of some products you wish to display and sell to your users. In this example, we'll assume you want to just display a bunch of photographs with a caption below. This can of course be adapted to other uses also.

Solution:

The simplest way to provide a solution for this problem is to make use of Grav's media functionality which allows a page to be aware of the images available in its folder.

Let's assume you have a page you've called gallery.md and also you have a variety of images in the same directory. The filenames themselves are not important as we will just iterate over each of the images. Because we want to have extra data associated with each image, we will include a meta.yaml file for each image. For example, we have a few images:

Each of the .jpg files are a relatively good size that is appropriate for a full-size version, 1280px x 720px in size. Each of the meta.yaml files contain a few key entries, let's look at fido-playing.jpg.meta.yaml:

title: Fido Playing with his Bone
description: The other day, Fido got a new bone, and he became really captivated by it.

You have complete control over what you put in these meta files, they can be absolutely anything you need.

Now we need to display these images in reverse chronological order so the newest images are shown first and display them. Because our page is called gallery.md we should create an appropriate templates/gallery.html.twig to contain the rendering logic we need:

Basically, this extends the standard partials/base.html.twig (assuming your theme has this file), it then defines the content block and provides the content for it. The first thing we do is echo out any page.content. This would be the content of the gallery.md file, so it could contain a title, and a description of this page.

The next section simply loops over all the media of the page that are images. We are outputting these in an unordered list to make the output semantic, and easy to style with CSS. we are assigning each image the variable name image and then we are able to perform a simple cropResize() method to resize the image to something suitable, and then below it, we provide an information section with the title and description.

You could make a more advanced gallery-implementation by using creating filters for camera-data, with the EXIF-function.

Render content in columns

Problem:

A question that has come up several times is how to quickly render a single page in multiple columns.

Solution:

There are many potential solutions, but one simple solution is to divide up your content into logical sections using a delimiter such as the HTML <hr /> or thematic break tag. In markdown, this is represented by 3 or more dashes or ---. We simply create our content and separate our sections of content with these dashes:

You can see how the content is being split by the <hr /> tag and converted into an array of 3 columns which we loop over and render. In this example we are using a simple HTML table tag, but you could use anything you wish.

Really simple css image slider

Problem:

You need an image slider without any overhead.

Solution:

This recipe is for 4 images and a page called slider.md! Simply put the images where the .md file is. Next, create a new Twig template and extend base.html.twig.

Add a recent post widget to your sidebar

Problem:

You want to create a recent post widget on the sidebar

Solution:

It's always possible to create a partial template extending partials/base.html.twig (see other solutions on this page), but here you're going to create a full template instead. The final code for your Twig template is shown below:

All this code does is sort the children (blog posts) of the /blog page by decending date order. It then takes the first five blog posts using the slice Twig filter. By the way, slice(n,m) takes elements from n to m-1. In this example, any blog posts that have a banner image have been named banner.jpg. This is set in a variable bannerimage. If bannerimage exists, it is shrunk down to a 60px x 60px box and will appear to the left of the post title text and date. If it does not exist, the website logo is resized to 60px x 60px and placed to the left of the title and date text instead.

Override the default logs folder location

The default location for the logs output of Grav is simply called logs/. Unfortunately, there are instances where that logs/ folder is already used or is off-limits. Grav's flexible stream system allows the ability to customize the locations of these folders.

First, you need to create your new folder. In this example, we'll create a new folder in the root of your Grav install called grav-logs/. Then create a new root-level file called setup.php and paste the following code:

This creates an ordered list which iterates over all visible pages within Grav, going three levels deep to create a structure for each level. The list wrapped around the entire structure has the class tree, and each list-item has the class parent if it contains children or item if it does not.

Clicking on a parent opens the list, whilst regular items link to the page itself. You could add this to virtually any Twig-template in a Grav theme, provided that Grav can access the visible pages.

This should generally be placed before the Twig-structure, or ideally be streamed into the Asset Manager in your theme. The effect is to add [+] after each parent-item, indicating that it can be opened, which disappears when opened.

This should always be placed after the Twig-structure, also ideally in the Asset Manager.

Dynamically style one or more pages

You can dynamically style different pages/posts in your Grav site (independent of template file assignment) by customizing a Theme's Twig file to apply a CSS class passed as a variable in a page's FrontMatter.

You can style different posts/pages in your Grav site by two methods:

If you are using the Antimatter theme, you can use the existing body_classes header property to set your custom CSS class for that page

If you are using a theme not based on Antimatter (or not implementing body_classes as it does), you can customize a Theme's Twig file to apply a CSS class passed as a variable in a page's header property

For example, in your theme's base.html.twig file or a more specific template such as page.html.twig file you could add a class to the display of page content, such as:

<div class="{{ page.header.body_classes }}">
...
</div>

Then, for each page you wish to have a unique style, you would add the following header property (assuming you have defined a CSS class for featurepost):

body_classes: featurepost

Note: This is how the Antimatter theme applies page-specific classes, and so it's a good standard to follow.

Migrate an HTML theme to Grav

Migrating an HTML theme to Grav is a common task. Here is a hands-on step-by-step process that can be used to achieve this goal.

You probably have downloaded the theme, and it's composed of several HTML files. Let's start with simply making Grav load the home page. No custom content, just replicate the HTML theme, but within a Grav structure.

Create a templates/home.html.twig Twig template inside the theme’s templates folder. This will represent a template specific for the home page. Usually, the home is a unique page on the site, so it probably deserves a dedicated Twig file.

Copy the HTML code from the template's home page, starting at <html> and ending at </html> to your new home.html.twig file.

Create a pages/01.home/home.md empty file. Now point your browser to yoursite.com/home: it should show up the content, but the CSS, JS and images will not be loaded, probably because the theme has them hardcoded as /img/* or /css/* links.

Adding the correct asset links

In Grav the links are broken because they point to the home route, so instead of pointing to /user/themes/mytheme/img, they point to /img in the Grav root. Since it's best to keep all theme-related assets inside the theme, we need to point Grav to the correct location.

Search within the page for assets and change the images references from img/*.* to <img src="{{ url('theme://img/*.*', true) }}" />.

Stylesheets require a bit more thought as there’s an asset pipeline we’ll want to enable at some point, so we move them to a stylesheets block within the <head> tag.

The page changes should now be shown in your Browser. If not, make sure that the pages cache and the twig cache are disabled in the Grav system configuration settings.

This is just the start. Now you might need to add more pages, and come up with better ways to present the content of your pages using the header FrontMatter, and custom Twig that processes usual building blocks need: the home page testimonials, reviews, the product features and so on.

Adding another page

To add another page, the process is similar. For example, let's say you want to next create the blog page.
Repeat the process to add a templates/blog.html.twig file, paste the HTML source, and create a pages/02.blog/blog.md page.

Now, while images links inside the pages still need to be migrated to Grav's assets syntax (or simply change the path), you don't want to repeat the same work you did above for CSS and JS assets. This should be reused across the site.

Shared Elements

Identify the common parts of the pages (header and footer), and move them to the templates/partials/base.html.twig file.

Each page template then needs to extend partials/base.html.twig (https://github.com/getgrav/grav-theme-antimatter/blob/develop/templates/default.html.twig#L1) and just add their unique content.

Add an asset to a specific page

Problem

You need to add an asset to a specific template on your theme.

Solution

Most of the time, your assets will be added inside a twig block in your base template like below.

In order to add your asset, you have to extend this block in your template and call {{ parent() }} which will get the assets already added in your base template.
Let's say you want to add a "gallery.js" file on your "Portfolio Gallery" page.
Edit your template and add your asset with the {{ parent() }}.

Reuse page or modular content on another page

Problem:

You have many pages or modules and would like to share the same content block on more than one page without having to maintain multiple separate instances of the same text.

Solution:

This is a very simple straightforward method which does not require a plugin and can be used within the admin panel.

Note: There is also plugin Grav Page Inject Plugin for this functionality which may be suitable for more advanced scenarios.

First, create a new template file to act as a placeholder for the content - it can have any name, this one is named "modularreuse" and will be in the stored in your theme's templates/modular folder for this example but can be stored anywhere in the templates folder.

modular_reuse.html.twig contains only one line:

{{ page.content }}

Next, create a new modular page in the admin panel where this content should be displayed using this new "modular reuse" template. The new page name can be anything you like as it will not be displayed - the original page title will be output.

What comes after "include" is where the template from step one is stored, probably in the templates folder for pages in the templates/modular folder for modulars.

After page.find should come the actual link to the original content that you want to reuse. Modular content starts with an _ but pages do not. The easiest way to find the correct link is to open the page in the admin panel and copy the url after the word admin.

Now "amazing offers" can be displayed in multiple places but only needs to be updated once.

Make a custom anti-spam field for your contact form

Problem:

Normal methods of spam-prevention, like the honeypot-field, is bypassed by certain spam-bots.

Solution:

Make it harder for the bot to guess what it can and can't fill it in, when filling out the contact form. Put simply, ask a question the user won't fail to answer, but whose answers are hard for a bot to understand the significance of. In your Markdown-file with Form-data
, add this field:

The question should be something simple, but with multiple simple wrong answers accompanying it. What matters is the order of the answers. The right answer should never be the first one; aim for somewhere in the middle. It's important to randomize the values behind the answers (labels) yourself, so a database of associated values and answers won't help in answering.

Bots get smarter all the time, but they tend to forego trying to answer the same question several times if the first attempt fails. Also, even the smartest of them rely on dictionaries of known data to guess at an answer. We ask a simple question, "What is five times eight?", and give three options, "32", "40", and "48". The right answer is obviously "40", but instead of checking the bot's math-skills, we're assigning the values "alaska", "oklahoma", and "california" to these numbers, respectively. Because bots look at the possible values, rather than their label, the answers bears no relation to the question. You could even add an answer "Pineapple" with the value "mississippi" and validate against that, and just tell your users to choose that as their answer. The point is to personalize the randomization of data.