Tutorials https://design.briarmoon.ca/tutorials en Getting the parent node of a Drupal 8 node https://design.briarmoon.ca/tutorials/drupal-8/getting-the-parent-node-of-a-drupal-8-node <span class="clearfix field field--name-title field--type-string field--label-hidden">Getting the parent node of a Drupal 8 node</span> <span class="clearfix field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">NickWilde</span></span> <span class="clearfix field field--name-created field--type-created field--label-hidden">Sat, 12/16/2017 - 09:16</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>There are some circumstances that you want to get data from the node one level up a menu from your current node. However, doing that is not self-explanatory since there isn't actually any direct relationship between the nodes.I'll be referring to the nodes as "parent" and "child" for simplicity despite this. For example, consider this snippet of a menu from /admin/structure/menu/manage/main <img alt="Screenshot of a menu admin page in Drupal 8" data-entity-type="file" data-entity-uuid="279535a4-c12e-46ed-b3ec-3b69181dfe0d" src="/sites/default/files/inline-images/Screenshot-2017-12-16%20Edit%20menu%20Main%20navigation%20BriarMoon%20Farm.png" /></p> <p>"Goats" would link to the "parent" node, while "Our Policies" would link to the "child" node.</p> <p>My personal use case was getting a hero image from the parent node if the child node didn't have one. But it could be applied, to get any data from the parent. You could also, reverse the process and get data from the child although then you would have to filter it to <em>what</em> child node/menu item whereas menu items only have one parent.</p> <p>This basic technique can be done from either a module or a theme although depending on the hook you need it in, you may need to use a module - but most hooks you'd want to use it in work in themes. You could also use it in OOP based code in a module although then you'd want to inject some of the services. For the purpose of this tutorial, we'll presume you've created a new theme - if you don't know how to do that, see <a href="https://www.drupal.org/docs/8/theming-drupal-8/creating-a-drupal-8-sub-theme-or-sub-theme-of-sub-theme">Creating Drupal a 8 sub-theme</a>.</p> <h2 id="pre-requisites">Pre-requisites</h2> <ul><li>Functioning Drupal 8 install</li> <li>Some Drupal 8 knowledge</li> <li>Moderate PHP comfort.</li> <li>Custom module or theme to add it to.</li> </ul><h2 id="setup">Setup</h2> <p>Since Drupal 8 uses namespaces and autoloading (because they make so much easier), you'll have to add a use statement to the top of the file:</p> <pre> <code>use Drupal\node\Entity\Node; </code></pre> <p>Then you have to decide what hook you need to use it in. This is mostly based on what you are doing with this data. In my case, I used <a href="https://api.drupal.org/api/drupal/core%21includes%21theme.inc/function/template_preprocess_region/">template_preprocess_region()</a> however, you can use any hook - or class. A frequently ideal choice, would probably be <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21node.module/function/template_preprocess_node/8.4.x">template_preprocess_node()</a> - it also means the node is guaranteed to be available.</p> <p>With whatever hook we're using, the first thing we need to do is get the current node's id. If using <code>template_preprocess_node</code>, that'd look like:</p> <pre> <code>function MY_THEME_preprocess_node($var) { $node_id = $var['node']-&gt;id(); ...</code></pre> <p>For most other hooks, you'll have to get the node via the ::request() service. There is one important hangup here that although easy to avoid you have to know: there are cases when the request includes a node ID instead of a loaded node object. So if you then try to do operations with this "object" you'll get a fatal error. So in that case, your code would look like:</p> <pre> <code>function MY_THEME_preprocess_region($var) { // This will only be true if it is a node route. if ($node = \Drupal::request()-&gt;attributes-&gt;get('node')) { if (is_string($node)) { $node_id = $node; } else { $node_id = $node-&gt;id(); } ...</code></pre> <p>Now we get to the part that is least straight forward. Nodes don't actually have a direct relationship with Menu Links. However, the menu link manager service will get links that match various filters. So next, we get the menu link manager service, get the relevant link, then get the parent menu item. Finally from the parent menu item, if it refers to a node, we get that node. Continuing that function, next we add:</p> <pre> <code> /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); $links = $menu_link_manager-&gt;loadLinksByRoute('entity.node.canonical', ['node' =&gt; $node_id]); // Because loadLinksByRoute() returns an array keyed by a complex id // it is simplest to just get the first result by using array_pop(). /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = array_pop($links); // Now check if the menu has a parent menu item and if so load it. /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */ if ($link-&gt;getParent() &amp;&amp; $parent = $menu_link_manager-&gt;createInstance($link-&gt;getParent())) { // Finally, we figure out if the parent menu item refers to another node // and if so, load it. $route = $parent-&gt;getUrlObject()-&gt;getRouteParameters(); if (isset($route['node']) &amp;&amp; $parent_node = Node::load($route['node'])) { // We now have a fully loaded node in the $parent_node variable and can // get whatever data we need from it. } }</code></pre> <h2 id="straight-forward-code">Straight forward code</h2> <p>That code above is extra commented to explain what is going on and split into chunks that are hard to copy and past. So here is the completed function if you're using <code>template_preprocess_region</code>.</p> <pre> <code>/** * Implements template_preprocess_region(). */ function MY_THEME_preprocess_region($var) { if ($node = \Drupal::request()-&gt;attributes-&gt;get('node')) { if (is_string($node)) { $node_id = $node; } else { $node_id = $node-&gt;id(); } /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); $links = $menu_link_manager-&gt;loadLinksByRoute('entity.node.canonical', ['node' =&gt; $node_id]); /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = array_pop($links); /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */ if ($link-&gt;getParent() &amp;&amp; $parent = $menu_link_manager-&gt;createInstance($link-&gt;getParent())) { $route = $parent-&gt;getUrlObject()-&gt;getRouteParameters(); if (isset($route['node']) &amp;&amp; $parent_node = Node::load($route['node'])) { // Do something with teh $parent_node here. } } } }</code></pre> <p>Any questions? Something unclear? Something you disagree with? Leave a comment below.</p> </div> <div class="clearfix field field--name-field-audience field--type-entity-reference field--label-inline"> <div class="field__label">Audience</div> <div class="field__item">Back-End Development</div> </div> <div class="clearfix field field--name-field-difficulty field--type-entity-reference field--label-inline"> <div class="field__label">Difficulty</div> <div class="field__item">Moderate</div> </div> <div class="clearfix field field--name-field-recommended-skills field--type-entity-reference field--label-inline"> <div class="field__label">Recommended Skills</div> <div class="field__items"> <div class="field__item">PHP</div> </div> </div> <div class="clearfix field field--name-field-tutorial-subject field--type-entity-reference field--label-above"> <div class="field__label">Tutorial Subjects:</div> <div class="field__items"> <div class="field__item"><a href="/tutorials/drupal/drupal-8" hreflang="en">Drupal 8</a></div> </div> </div> <section class="clearfix field field--name-comment field--type-comment field--label-above comment-wrapper"> <h2 class="title comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=18&amp;2=comment&amp;3=comment" token="nAj2keyfEt4_0fyhSgew93ONC0o92AbyNwt55od7XmU"></drupal-render-placeholder> </section> Sat, 16 Dec 2017 17:16:13 +0000 NickWilde 18 at https://design.briarmoon.ca Setting up the brotli extension for PHP https://design.briarmoon.ca/tutorials/drupal/setting-up-the-brotli-extension-for-php <span class="clearfix field field--name-title field--type-string field--label-hidden">Setting up the brotli extension for PHP</span> <span class="clearfix field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">NickWilde</span></span> <span class="clearfix field field--name-created field--type-created field--label-hidden">Fri, 12/15/2017 - 18:06</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>The <a href="https://github.com/kjdev/php-ext-brotli">Brotli extension for PHP</a>, integrates the Brotli compression algorithm for use in PHP. Brotli compression is similar to gzip but has a higher compression ratio - up to 30% bandwidth savings. However, it does have a slight drawback; it is slower. This can be worked around most effectively by pre-compressing resources before the web server needs them. Hence with a LAMP stack, using the Brotli PHP extension provides easy performance gains.</p> <p>Brotli has been found to have significant performance gains for end user most especially those with lower quality internet such as much of rural North America, mobile users and users in developing markets. One high quality analysis and large scale implementation that has been <a href="https://engineering.linkedin.com/blog/2017/05/boosting-site-speed-using-brotli-compression">well documented</a> was done in early 2017. Browser support is not yet universal although it is rapidly increasing. In April 2017, support was ~56% of users, as of December 2017, that support level is now up to 72%. <img alt="CanIUse table showing browser support as of 2017-12-05 for brotli." data-entity-type="file" data-entity-uuid="cbb51aae-1c6b-4eee-b8ac-873af3340c77" src="/sites/default/files/inline-images/Screenshot-2017-12-15%20Can%20I%20use%20Support%20tables%20for%20HTML5%2C%20CSS3%2C%20etc.png" /></p> <p>For large scale sites there may be many extra considerations such as does your CDN support brotli or will you have to do various tricks to get it to work. But for small to medium sites, usually, it will just work. With Drupal 8, all you need is the the <a href="https://drupal.org/project/advagg">Advanced Aggregates</a> module and the Brotli PHP extension installed and it handles the rest of it - fully automatically for Apache servers. For other PHP based sites, you'll probably have to make use of the functions yourself, but that is beyond the scope of this tutorial (although I can assist with that.)</p> <h2 id="pre-requisites">Pre-requisites</h2> <ul><li>Some command line comfort.</li> <li>Modern Ubuntu or Debian install (other nix variants will also work, but there may be differences).</li> <li>User with sudo privileges. If you don't have that, see <a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-sudo-user-on-ubuntu-quickstart" target="_blank">Creating a Sudo User</a>.</li> <li>PHP 7.1. Any modern PHP version will work, but in a few of the commands, you'll have to change <code>7.1</code> to match your version. The source doesn't matter although I am somewhat assuming <a href="https://launchpad.net/~ondrej/+archive/ubuntu/php">Ondřej Surý's PPA</a>. If using a different source, paths may be slightly different.</li> </ul><h2 id="getting-your-development-environment-ready">Getting your development environment ready</h2> <p>If you've already built any PHP extensions, your environment is likely good to go. If, however, you've never built any PHP extensions, you will likely need to do a bit of setup. The setup on Debian based systems is very easy. So you will likely have to install the PHP development extensions. From here on, you'll be working in the command line, so open bash (or your preferred command line).</p> <p>To install the PHP development extensions, just run:</p> <pre> <code>sudo apt install php7.1-dev</code></pre> <p>confirming that you do want to install the possibly large number of required packages. Depending on prior installations, your connection speed etc, this may take a while to install and setup.</p> <h2 id="building">Building the extension</h2> <p>Now, you need to clone the repository and it's sub repositories.</p> <pre> <code>git clone --recursive --depth=1 https://github.com/kjdev/php-ext-brotli.git cd php-ext-brotli</code></pre> <p>Next, it needs to be set up for your version of PHP, build scripts configured and the extension gets built. That may sound complicated, but it is very easy and needs no hand coding or user intervention. Just run:</p> <pre> <code>phpize ./configure sudo make install clean</code></pre> <p>This will build and move the module file <code>brotli.so</code> to <code>/usr/lib/php/20160303/</code>. If using a different PHP version, the date in that path will change to the PHP API version date. Now to enable it, there are a few different methods you can use. In my opinion the best one, is creating an ini file for it so that it can easily be enabled and disabled. So in your command line run:</p> <pre> <code>sudo nano /etc/php/7.1/mods-available/brotli.ini</code></pre> <p>Add the line <code>extension=brotli.so</code> and save it (<code>ctrl-O</code>). Now to enable it you can run</p> <pre> <code>sudo phpenmod brotli</code></pre> <p>Just want to know it is installed correctly from the command line? Run:</p> <pre> <code>php -a echo is_callable('brotli_compress');</code></pre> <p>That should return <code>1</code>.</p> <h2 id="advanced-usersoptions">Advanced Users/Options</h2> <h3 id="multiple-php-versions">Multiple PHP Versions</h3> <p>Depending on your system, or needs, you may want to build for multiple versions of PHP. If so, this can easily be done. Just install the PHP development files for each version you want to target, for example:</p> <pre> <code>sudo apt-install php7.0-dev php7.1-dev php7.2-dev</code></pre> <p>and run the standard build steps outlined but with a couple of minor adjustments for each version. - Use <code>phpize7.x</code> for each version - Use ./configure --with-php-config=php-config7.x - Creating the brotli.ini in <code>/etc/php/7.x/mods-available/</code> for your current version.</p> <h3 id="quick-code">Quick Code</h3> <p>Know what you're doing and just want copy &amp; paste commands without a bunch of verbiage?</p> <pre> <code>sudo apt install php7.1-dev -y git clone --recursive --depth=1 https://github.com/kjdev/php-ext-brotli.git cd php-ext-brotli phpize ./configure sudo make install clean sudo nano /etc/php/7.1/mods-available/brotli.ini</code></pre> <p>Add the line <code>extension=brotli.so</code>, save and run <code>sudo phpenmod brotli</code></p> </div> <div class="clearfix field field--name-field-audience field--type-entity-reference field--label-inline"> <div class="field__label">Audience</div> <div class="field__item">DevOps</div> </div> <div class="clearfix field field--name-field-difficulty field--type-entity-reference field--label-inline"> <div class="field__label">Difficulty</div> <div class="field__item">Moderate</div> </div> <div class="clearfix field field--name-field-recommended-skills field--type-entity-reference field--label-inline"> <div class="field__label">Recommended Skills</div> <div class="field__items"> <div class="field__item">Command Line</div> </div> </div> <div class="clearfix field field--name-field-tutorial-subject field--type-entity-reference field--label-above"> <div class="field__label">Tutorial Subjects:</div> <div class="field__items"> <div class="field__item"><a href="/tutorials/drupal" hreflang="en">Drupal</a></div> <div class="field__item"><a href="/tutorials/devops" hreflang="en">DevOps</a></div> <div class="field__item"><a href="/tutorials/php" hreflang="en">PHP</a></div> </div> </div> <section class="clearfix field field--name-comment field--type-comment field--label-above comment-wrapper"> <h2 class="title comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=17&amp;2=comment&amp;3=comment" token="B_jr1cz7gicq8lxY4PqWeJI7K26EGwp7nNbZxPjUVjE"></drupal-render-placeholder> </section> Sat, 16 Dec 2017 02:06:24 +0000 NickWilde 17 at https://design.briarmoon.ca https://design.briarmoon.ca/tutorials/drupal/setting-up-the-brotli-extension-for-php#comments Including Patches in Drupal Contrib Modules https://design.briarmoon.ca/tutorials/drupal/including-patches-in-drupal-contrib-modules <span class="clearfix field field--name-title field--type-string field--label-hidden">Including Patches in Drupal Contrib Modules</span> <span class="clearfix field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">NickWilde</span></span> <span class="clearfix field field--name-created field--type-created field--label-hidden">Tue, 01/24/2017 - 18:43</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Very occasionally in building a Drupal module (especially for 7.x), you can't actually quite do what you want to do without a change to another module or core. Every effort should be made to avoid that but there are instances that it is truly required and the right method. If on your own site it is pretty easy to document, commit to version control etc - still questionable but doable. However if it is for a public module, it has been problematic at best but there is a way to reduce the pain.</p> <p>The traditional method has been in your read-me and/or module pages stating that it requires x changes to x files or sometimes giving a link to an issue in the queue or patch (for example see <a href="https://www.drupal.org/project/ooe">OOE</a>). This does work but is an annoying method and is verging on <a href="https://cph2010.drupal.org/sessions/customizing-drupal-without-killing-kittens.html">killing kittens</a> (<a href="https://www.drupal.org/docs/7/site-building-best-practices/never-hack-core">hacking core</a>) territory. The problem is that many of your users may do the change, then forget about it when updating core and then suddenly module doesn't work, possibly even causing dreaded <abbr title="White Screen of Death">WSOD</abbr>. Providing those instructions, warnings and all is something you will still have to do because unfortunately the better method will not work for all users.</p> <p>The better method: use Composer's patches functionality (provided by <a href="https://github.com/cweagans/composer-patches">cweagans/composer-patches</a>).</p> <p>Most people using composer to manage their Drupal installations are already using it as it is required in the <a href="https://github.com/drupal-composer/drupal-project">Composer template for Drupal Projects</a>. So how is it better:</p> <ul><li>Patch is automatically applied (if possible) every time an update is downloaded.</li> <li>Almost no end user work is required to apply/use.</li> </ul><p>You basically have to add very minimal of instructions to your project readme/project page. For example:</p> <blockquote> <p>This module requires x patch: &lt;link&gt;<br /> You can manually make the required changes (probably a bunch more detailed instructions on how to apply/manually make changes) or if you are using composer, enable patching. To enable patching just run</p> <pre> composer config extra.enable-patching true</pre> <p>prior to installing this module and the patch will be automatically &amp; safely applied.</p> </blockquote> <p>So that is very simple for anyone already using composer and doesn't add much noise for non-composer users. It is also very easy to set up on your end; add a composer.json to your module (if it doesn't have one the Drupal packagist endpoints will generate one, otherwise it will automatically use your own) and add a few specific settings. For example here's a minimal one, used in the <a href="https://drupal.org/project/field_states_ui">Field States UI</a> module (mostly written by myself):</p> <pre> { "name": "drupal/field_states_ui", "description": "Provides a UI for applying field states.", "type": "drupal-module", "homepage": "https://www.drupal.org/project/field_states_ui", "authors": [ { "name": "Nick Wilde", "homepage": "https://www.drupal.org/u/nickwilde" }, { "name": "See other contributors", "homepage":"https://www.drupal.org/node/2776325/committers" } ], "support": { "issues": "https://www.drupal.org/project/issues/field_states_ui", "source": "http://git.drupal.org/project/field_states_ui.git" }, "license": "GPL-2.0+", "require": { "cweagans/composer-patches": "~1.0", "drupal/core": "8.x" }, "extra": { "patches": { "drupal/core": { "multi-value field widget hook": "https://www.drupal.org/files/issues/no_hook_to_edit-2822460-2.patch" } } } }</pre> <p>You must require cweagans/composer-patches and then add the patches. To add the patches, you just add them as individual "description" : "link to patch" lines under the project they are patching under patches in the extra section.</p> <p>So in summary it is very easy to set up on your end, will save time for some of your users and won't affect the others so why wait? If you have to patch core or contrib use this method now. Any questions? Feedback? examples of use? We'd love to hear from your below, or on <a href="https://twitter.com/BriarMoonDesign">Twitter</a>!</p> </div> <div class="clearfix field field--name-field-audience field--type-entity-reference field--label-inline"> <div class="field__label">Audience</div> <div class="field__item">Module Developers</div> </div> <div class="clearfix field field--name-field-difficulty field--type-entity-reference field--label-inline"> <div class="field__label">Difficulty</div> <div class="field__item">Moderate</div> </div> <div class="clearfix field field--name-field-recommended-skills field--type-entity-reference field--label-inline"> <div class="field__label">Recommended Skills</div> <div class="field__items"> <div class="field__item">Command Line</div> </div> </div> <div class="clearfix field field--name-field-tutorial-subject field--type-entity-reference field--label-above"> <div class="field__label">Tutorial Subjects:</div> <div class="field__items"> <div class="field__item"><a href="/tutorials/drupal" hreflang="en">Drupal</a></div> <div class="field__item"><a href="/tutorials/composer" hreflang="en">Composer</a></div> <div class="field__item"><a href="/tutorials/patches" hreflang="en">Patches</a></div> </div> </div> <section class="clearfix field field--name-comment field--type-comment field--label-above comment-wrapper"> <h2 class="title comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=13&amp;2=comment&amp;3=comment" token="IpQiXavlekgUkoMY55I96KcK_vUoClR5uCQ4Y4laa90"></drupal-render-placeholder> </section> Wed, 25 Jan 2017 02:43:13 +0000 NickWilde 13 at https://design.briarmoon.ca