Learn

Share Your Knowledge!

This fast-growing section of our site is the new one-stop shop for educational materials for ExpressionEngine with rich and expert content, from both EllisLab and the community. Submit your article, tip, or solution today!

  • MySQL 8 and ExpressionEngine: Tips for the Trailblazers

    in: Tips

    NOTE: This article was written October 2, 2018 when MySQL 8 was very new. This article may quickly become stale as PHP and MySQL progress.

    MySQL 8 is cutting edge. And developers who want to try out the new features can expect a smooth ride in ExpressionEngine—with minor tweaks to their MySQL settings.

    The biggest incompatible change to MySQL 8 is in how it authenticates your password. The default is a new plugin called caching_sha2_password. This results in an error in all sorts of applications, including desktop MySQL browsers:

    The server requested authentication method unknown to the client [caching_sha2_password] …

    Since most versions of PHP do not recognize this new plugin†, you’ll need to change MySQL to authenticate the old way. Use a my.cnf configuration file to restore the old way. In your my.conf in the [mysqld] section add:

    default_authentication_plugin=mysql_native_password

    Restart the server after editing the option file. Run the query SHOW VARIABLES; and look for the default_authentication_plugin value to verify that your change took.

    If the value is correct and you still get that pesky error, then the MySQL user probably needs to be modified as well. To change the user’s authentication plugin, use the ALTER USER command:

    ALTER USER 'jeffrey'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

    (replacing password with your actual password, of course). And that should take care of it!

    † From the PHP manual:

    PHP version before 7.1.16, or PHP 7.2 before 7.2.4, set MySQL 8 Server’s default password plugin to mysql_native_password or else you will see errors similar to The server requested authentication method unknown to the client [caching_sha2_password] even when caching_sha2_password is not used.

    | Read in 1 minute
  • Downloadable iCalendar Files

    in: Tips, Tutorials, Templating

    This tutorial will show you how to create downloadable iCalendar files. With ExpressionEngine, it’s easy-peasy because you can use your well-structured content however you need. You can use the same entries you use for your web site to create shareable .ics iCalendar format files. Here’s how in three easy steps.

    1. Install the “Download Content” plugin.
    2. Create your iCalendar format template.
    3. Build a link to your iCalendar template.

    Install the free Download Content plugin

    In order to make your ExpressionEngine-generated .ics files be treated like a downloadable file, grab and install the free Download Content plugin.

    • Download the plugin from GitHub.
    • Unzip and upload the enclosed download_content folder to /system/user/addons.
    • Visit the Add-on Manager in your control panel, and click Install for the “Download Content” plugin.

    Create your download template

    Now we need to create an endpoint that we will link to that allows our visitors to download our .ics file. In this example, I’m creating an iCalendar file for a free concert the local brewpub is putting on. I have created a template named add-to-my-calendar inside my concerts template group.

    It uses the entry date and expiration date of the concert, the title, and a custom field called meta_description. Side note: meta_description is part of my standard set of SEO fields that I add to every channel. Repurposing it for the description of the calendar event makes sense. I’m also going to take advantage of a few iCalendar fields to setup a default alarm two hours before the event.

    {exp:channel:entries channel='concerts' limit='1' show_future_entries='yes' require_entry='yes'}
    	{if no_results}
    		{redirect='404'}
    	{/if}
    
    {exp:download_content filename='{url_title}.ics'}
    BEGIN:VCALENDAR
    VERSION:2.0
    BEGIN:VEVENT
    UID:downloadable-icalendar-files-{entry_date format='%Y%m%d'}@{site_name:url_slug}
    DTSTART;TZID={entry_date format='%e:%Y%m%dT%H%i%s'}
    DTEND;TZID={expiration_date format='%e:%Y%m%dT%H%i%s'}
    SUMMARY:{title}
    X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
    LOCATION:The BrewPub\n62950 NE 18th St\nBend\, OR 97701
    BEGIN:VALARM
    X-WR-ALARMUID:downloadable-icalendar-files-{entry_date format='%Y%m%d'}@{site_name:url_slug}
    TRIGGER:-PT120M
    DESCRIPTION:{meta_description}
    ACTION:DISPLAY
    END:VALARM
    END:VEVENT
    END:VCALENDAR
    {/exp:download_content}
    
    {/exp:channel:entries}

    Let’s break down the tags:

    {exp:channel:entries channel='concerts' show_future_entries='yes' limit='1' require_entry='yes'}
    
    • show_future_entries= lets us use the Entry Date as the start time of the event. Since we want people to see events that haven’t happened yet, we tell the Channel Entries tag to show future events.
    • limit='1' require_entry='yes' is a common design pattern for single-entry templates. This makes sure we only show one entry, and we don’t want it to display anything if the URL title is invalid. We combine it with:
    {if no_results}
        {redirect='404'}
    {/if}
    

    If there are no results—an invalid URL, an entry that’s already expired, a closed status, etc.—we will have proper 404 behavior.

    The next tag is the Download Content plugin we installed earlier.

    {exp:download_content filename='{url_title}.ics'}
    

    Using the entry’s URL title for the filename isn’t a requirement, but it will help the visitor identify the file’s purpose after downloading. It needs an .ics extension so desktop and mobile calendar apps know that the file is for them. This plugin makes sure the output only contains what’s inside the plugin’s tag, and gets treated as a download instead of displaying it on the screen.

    iCalendar files are picky about whitespace. This is why the code is not indented like we would normally encourage.

    The rest of the contents are made from standard iCalendar file format attributes. You can read more about that file format and its attributes here: iCalendar file format. Here are some notes to help dissect our template decisions:

    • UID: Providing a UID allows the calendar app to change the existing event if it is updated. Otherwise adding a new version will create a new event. The URL title, date, and site name should give us a unique ID. The :url_slug variable modifier replaces spaces with slashes and so forth so we can use the site’s name here.
    • DTSTART/DTEND: The format parameter used here (%e:%Y%m%dT%H%i%s) will output: America/Los_Angeles:20180827T105300
    • We use the entry_date for the start date and the expiration_date for the end date.
    • SUMMARY uses the entry’s title for the event’s name
    • Some calendar apps may not use the DESCRIPTION, but we add it for those that do. Likewise, we add DTSTAMP because Outlook demands it.
    • TRIGGER:-PT120M sets an alarm for 120 minutes (2 hours) before the event
    • X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC sets an alarm for Apple clients when the person needs to leave their current location to make it to the event on time.

    Create a download link

    Now all we need to do is link to our .ics file and we’re good to go! From my concert listing page, I’ll add:

    <a href="{url_title_path='concerts/add-to-my-calendar'}">Add this concert to my calendar!</a>
    

    Now when I click the link, it downloads a properly formatted .ics file that I can add to my calendar!

    With this knowledge you can create events, todos, journal entries, and any other kind of iCalendar file you can imagine. Use these same principles to output your content in other file formats, too; the sky’s the limit! With ExpressionEngine, you never have to work hard to make your content work hard for you.

    | Read in 5 minutes
  • Smarter Breadcrumbs with Layout Lists

    in: Front-end Development, Tips, Templating

    Layout List variables make it easy to reuse content in a variety of ways.

    Define the breadcrumb name, URL and position on your content templates:

    {layout:set:append name='breadcrumb_urls'}{path='second'}{/layout:set:append}
    {layout:set:append name='breadcrumb_titles'}Second crumb{/layout:set:append}
    {layout:set:append name='breadcrumb_jsonld_positions'}2{/layout:set:append}
    
    {layout:set:append name='breadcrumb_urls'}{path='active'}{/layout:set:append}
    {layout:set:append name='breadcrumb_titles'}Active crumb{/layout:set:append}
    {layout:set:append name='breadcrumb_jsonld_positions'}3{/layout:set:append}

    Output it in whatever format you need on your layouts.

    Bootstrap’s Modern Business template’s HTML breadcrumbs:

    {layout:breadcrumb_urls}
        {if count == 1}
            <ol class="breadcrumb">
                <li class="breadcrumb-item">
                    <a href="index">Home</a>
                </li>
                <li class="breadcrumb-item">
                    <a href="{value}">{layout:breadcrumb_titles index='{index}'}</a>
                </li>
        {if:elseif count < total_results}
                <li class="breadcrumb-item">
                  <a href="{value}">{layout:breadcrumb_titles index='{index}'}</a>
                </li>
        {if:else}
                <li class="breadcrumb-item active">
                    {layout:breadcrumb_titles index='{index}'}
                </li>
            </ol>
        {/if}
    {/layout:breadcrumb_urls}

    A structured markup BreadcrumbList:

    <script type="application/ld+json">
        {
            "@context": "http://schema.org",
            "@type": "BreadcrumbList",
            "itemListElement": [
            {
                "@type": "ListItem",
                "position": 1,
                "item": {
                    "@id": "https://example.com/index",
                    "name": "Home"
                }
            }
            {layout:breadcrumb_urls}
            ,{
                "@type": "ListItem",
                "position": {layout:breadcrumb_jsonld_positions index='{index}'},
                "item": {
                    "@id": "{value}",
                    "name": "{layout:breadcrumb_titles index='{index}'}"
                }
            }
            {/layout:breadcrumb_urls}
        ]}
    </script>

    DRY, flexible, easy to maintain and update. Your final result will look like:

    <ol class="breadcrumb">
        <li class="breadcrumb-item">
            <a href="index">Home</a>
        </li>
        <li class="breadcrumb-item">
            <a href="second">Second crumb</a>
        </li>
        <li class="breadcrumb-item active">
            Active crumb
        </li>
    </ol>
    
    <script type="application/ld+json">
        {
            "@context": "http://schema.org",
            "@type": "BreadcrumbList",
            "itemListElement": [
            {
                "@type": "ListItem",
                "position": 1,
                "item": {
                    "@id": "https://example.com/index",
                    "name": "Home"
                }
            },
            {
                "@type": "ListItem",
                "position": 2,
                "item": {
                    "@id": "https://example.com/second",
                    "name": "Second crumb"
                }
            },
            {
                "@type": "ListItem",
                "position": 3,
                "item": {
                    "@id": "https://example.com/active",
                    "name": "Active crumb"
                }
            }]
        }
    </script>
    | Read in 3 minutes
  • Easy Multi-device Local Development with xip.io

    in: Tips

    You are building a site locally and you’d like to quickly check your work on your iPad, iPhone, another computer, or maybe a client’s laptop. You could pay for a dynamic DNS service, setup a DNS server on your local network, or setup a proxy server perhaps. But there’s an easier way. The key is a “magic domain” service, xip.io, which lets you give meaningful names to your local IP addresses.

    In this example, we will make my local https://awesomesite.dev available to others on my network, including my client’s iOS devices.

    How to do it

    I have my Apache VirtualHost and /etc/hosts file on my iMac configured to use https://awesomesite.dev/ for local development. I’ll add the special xip.io address as a ServerAlias, using the local IP address of my iMac (10.0.1.4):

    ServerName awesomesite.dev
    ServerAlias awesomesite.dev.10.0.1.4.xip.io
    DocumentRoot /Users/derek/Sites/AwesomeSite

    I’m keeping the awesomesite.dev. prefix so I can use this naming system with all my projects and make it clear which VirtualHost I’m accessing. If you’re using MAMP Pro, just go to your Hosts tab and click the + button under the “Aliases” box to add the xip.io address as an alias. Notice that xip.io addresses follow the simple pattern of your domain name, followed by your local IP address, followed by xip.io.

    Now to make sure that links and asset requests go to the right URLs, at the bottom of my config.php file in ExpressionEngine, I add the following:

    if (preg_match('/.*\.xip\.io$/', $_SERVER['HTTP_HOST']))
    {
    	$config['base_url'] = "http://{$_SERVER['HTTP_HOST']}/";
    }

    This block of code tells ExpressionEngine to use the xip.io address as the basis for URLs, but only if the request came from a .xip.io address. The preg_match() conditional validates the HTTP_HOST header for us in this case.

    Use the special {base_url} variable in all of your URL settings which keeps your site ultra-portable. If you do not, then you will need to override site_url, cp_url, theme_folder_url, and upload_preferences for each Upload Destination.

    Never use HTTP_HOST without validating it! It is a request variable, which means it can be faked, and you can’t trust it. If you use it without validation, you run the risk of a cache poisoning attack. That means an attacker could cause all generated links to point to their own servers for people who access your site after they do. If they copy your design, they have an effective phishing attack on your site’s visitors. Always validate the HTTP_HOST header before using it! If you’re using a multi-environment config, make sure yours does this! Exclamation mark!

    Violá

    Make sure to restart your web server after updating your VirtualHost, then anytime you want to pull up your local site on any device on your network, just key the xip.io address into the browser, in my case https://awesomesite.dev.10.0.1.4.xip.io. Then I can work on my iMac and check it instantly on my phone, iPad, or Macbook Air, or let a client spin it up on their device.

    There are many ways to do this; xip.io is just one of the easy and seamless options.

    | Read in 4 minutes
  • Template Routes for Single Entry / Permalink URLs

    in: Tips, Tutorials, Templating

    Single entry permalink URLs are common on the web. We’re going to apply it to a blog here, as that’s readily understood, but the pattern will work for everything else as well.

    By default ExpressionEngine’s URLs work like this https://example.com/blog/entry/url-title, where “blog” is a template group, and “entry” is a template, which is fine. But nicer would be something like https://example.com/blog/url-title, cause who needs that extra entry segment anyway? You could handle all of this in the blog template group’s index, but that’s inelegant, full of conditionals, hard to read, and hard to maintain.

    Template Routes give us an easy to create and maintain implementation. What you’ll need is the following. A blog template group with the templates index and single-entry.

    Inside of index you’ll put your blog listing, this will cover paginated archives, and categorized listings as well—URLs like https://example.com/blog/P15 and https://example.com/blog/category/cat-url-title.

    Index might look something like this:

    {exp:channel:entries channel='blog' limit='25'}
    	<div class="blog-entry">
    		<h2><a href="{route='blog/single-entry' url_title='template-routes-for-single-entry-permalink-urls'}">{title}</a></h2>
    		<p>{entry_date format='%n/%j/%Y'}</p>
    		{if has_categories}
    			<p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    		{/if}
    		<p>by: {author}</p>
    	</div>
    
    	{if no_results}
    		{redirect='404'}
    	{/if}
    {/exp:channel:entries}

    See that route= variable above? It’ll come into play in a moment. Next you’ll need a single-entry template that will cover your, well, single entries. And will be reached with URLs like https://example.com/blog/url-title.

    Single Entry will look something like this:

    {exp:channel:entries channel='blog' limit='1' require_entry='yes'}
    	<h1>{title}</h1>
    	<p>{entry_date format='%n/%j/%Y'}</p>
    	{if has_categories}
    		<p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    	{/if}
    	<p>by: {author}</p>
    
    	{blog_content}
    
    	{if no_results}
    		{redirect='404'}
    	{/if}
    {/exp:channel:entries}

    Now that you have those set up, you need to add your route. Go to Developer > Template Manager > Template Routes and set up a route for the template single-entry Give it a Route of /blog/{url_title:regex[(((?!(P\d+|category\/)).)+?)]} select “no” for “Segments Required?” then save. The regex may look a little complex, so let’s break it down. (((?!(P\d+|category\/)).)+?) uses a negative lookahead assertion, so the route does not match if the second URL segment is either pagination (P\d+) or category/. We want those URL patterns to still use the ExpressionEngine default routing to the blog/index template.

    Now in your templates replace any path variables to your single-entry URLs to use the route= variable in the first code sample. This will make sure that when clicked the visitor is sent to the right place!

    That’s it! Now go forth and clean up your ExpressionEngine URLs today!

    | Read in 3 minutes
.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases