Drupal https://atendesigngroup.com/ en Upgrade paths for your Drupal 7 website https://atendesigngroup.com/articles/upgrade-paths-your-drupal-7-website <span>Upgrade paths for your Drupal 7 website</span> <figure> <picture> <source srcset="/sites/default/files/2021-06/UPGRADE_PATH_03.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-06/UPGRADE_PATH_03.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-06/UPGRADE_PATH_03.jpg?itok=P5hd_Wh6 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-06/UPGRADE_PATH_03.jpg?itok=RzZ7uKks 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-06/UPGRADE_PATH_03.jpg?itok=RzZ7uKks 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-06/UPGRADE_PATH_03.jpg?itok=RzZ7uKks 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-06/UPGRADE_PATH_03.jpg?itok=M4R5yUqr 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-06/UPGRADE_PATH_03.jpg?itok=M4R5yUqr 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-06/UPGRADE_PATH_03.jpg" alt="" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/eric-toupin" lang="" about="/user/eric-toupin" typeof="schema:Person" property="schema:name" datatype="">Eric Toupin</a></span> <span>Tue, 06/15/2021 - 14:14</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p><a href="https://www.drupal.org/psa-2020-06-24">Drupal 7 will reach end-of-life (EOL) in November of 2022</a>, which means that at least a half million webmasters &amp; site owners have some decisions to make. What’s the next step for your organization’s website? What sorts of costs might you be looking at for this upgrade? What timeline can you plan on for this change? All good questions.</p> <p>If you’re interested in this topic, take a moment to register for our Director of Engineering, Joel’s free webinar coming next week. He'll be covering aspects of these options in greater detail.</p> <div style="padding: 30px; border: 1px solid #e5e5e5;"> <p style="margin-bottom: 0;">Webinar, June 23rd:<br /> <strong>Options for upgrading your Drupal 7 or Drupal 8 site</strong></p> <p style="margin-top: 10px;"><a class="button" href="https://atendesigngroup.com/webinar/options-upgrading-your-drupal-7-or-drupal-8-site">Register Now</a></p> </div> <h3>What is EOL and what does it mean to me?</h3> <p>The Drupal ecosystem of core and contributed modules is protected against hackers, data miners, automated exploits and other malicious actors by the <a href="https://www.drupal.org/drupal-security-team/general-information">Drupal Security Team</a> — more than thirty developers working across three continents in almost a dozen countries to keep Drupal websites safe. The security team responds to reports of potential weaknesses in the Drupal core or contributed code and coordinates efforts to release new versions of the software that address those vulnerabilities.</p> <p>The more than a million Drupal developers worldwide going about their day-to-day development tasks act as a passive network of quality control agents. Developers who discover security vulnerabilities while working with the code can confidentially report them so that the security team can go about fixing the problem before knowledge of the vulnerability is widely available. A million worldwide developers backing a thirty-something strong team of elite developers spells security — for your website, your data, and your organization.</p> <p><b>In November of 2022, that all comes to an end for Drupal 7.</b> That’s when the security team will officially retire from the Drupal 7 project in favor of modern versions of the platform. As new vulnerabilities in the code are discovered (and made public) you won’t have anyone in your corner to fight back with new security releases.</p> <p>After EOL you can expect what’s left of the Drupal 7 community to move on, too. That means no new modules, new themes, or other new features built for the platform. It also means the pool of developers specializing in Drupal 7 starts to shrink very fast. If you’re still on Drupal 7 in late 2022, you’re out in the cold.</p> <p>The good news is that there are options. Here’s are my top three picks:</p> <hr /> <h3>Drupal 9: Drupal is dead, long live Drupal</h3> <p>Drupal 9 is the most modern iteration of the Drupal framework and CMS. It introduces a completely reimagined architecture and a rebuilt API more inline with modern development standards.</p> <ul> <li>Cost: High</li> <li>Build Time: High</li> <li>Longevity: High</li> <li>Support: High</li> </ul> <h4><b>Pros </b></h4> <p>Upgrading your site to the latest version of Drupal (9.1.10 at the time of this article) is, in most cases, the right move. Modern Drupal has grown to support a wide array of innovative features in core. Improved WYSIWYG content editing, a feature rich Media library, advanced publishing workflows, and rich JSON API are all available right out of the box. Couple that with Drupal’s new, highly modern architecture built on <a href="https://symfony.com/">Symfony</a>, the adoption of the <a href="https://twig.symfony.com/">Twig</a> templating engine, and dependency management via <a href="https://getcomposer.org/">Composer</a> and you really do <i>Build the best of the web</i> in terms of technology, support, and longevity.</p> <p>When you make the move to Drupal 9 you can count on Drupal’s huge and thriving community of developers (and security team) making the move right along with you. Existing modules from previous versions of Drupal are either — in almost all cases — already available, now packaged into core, or making their way to Drupal 9 at this moment. It’s very likely the agency you’re already working with has or is building a Drupal 9 proficiency, and it’s guaranteed that hundreds of other shops can pick up the slack in the odd case that your provider isn’t on board yet.</p> <p>Finally, let’s not forget <a href="https://dri.es/making-drupal-upgrades-easy-forever">Drupal’s commitment to easy future upgrades</a> which promises continuity in architecture that should facilitate easy upgrades to Drupal 10 and beyond. Gone are the days (probably) of “rebuild” style upgrades like those of Drupal 6 to Drupal 7, or Drupal 7 to Drupal 8/9.</p> <h4><b>Cons </b></h4> <p>Speaking of “rebuild” style upgrades... upgrading from Drupal 7 to Drupal 9 is one of them. While it could be your last major upgrade if you stick with Drupal for the long haul, moving from Drupal 7 to the more modern 8/9 and beyond architecture is a <i>very heavy lift</i>. For most organizations the move to Drupal 9 is the longest term, most feature-rich, most supported, and most modern option, but it generally entails <i>a complete rebuild of your site’s backend and theme</i> which basically means starting from scratch. Take a look at <a href="https://atendesigngroup.com/articles/drupal-7-drupal-8-9-and-beyond-your-last-major-upgrade">Joel’s post about the upgrade from Drupal 7 to Drupal 8</a> for more information — the process is comparable to a Drupal 7 to Drupal 9 upgrade.</p> <hr /> <h3>Backdrop CMS: The same, but different</h3> <p><a href="https://backdropcms.org/">Backdrop CMS</a> is a lightweight, flexible, and modernized platform built on Drupal 7 architecture with notable improvements. The software is a fork of the Drupal 7 code and boasts a simple, straightforward upgrade path.</p> <ul> <li>Cost: Low / Medium</li> <li>Build Time: Low / Medium</li> <li>Longevity: Medium / High</li> <li>Support: Medium</li> </ul> <h4><b>Pros </b></h4> <p>Drupal 7 sites moving to any other platform — including Drupal 8 / 9 — must be rebuilt. That’s not the case for Backdrop CMS, which gives you the option of protecting your investment in an existing Drupal 7 website <i>and</i> reaping the benefits of modern features like configuration management and advanced layout control. <a href="https://backdropcms.org/news/backdrop-cms-version-2-will-not-be-released-before-january-2025">Backdrop CMS will prioritize backward compatibility with Drupal 7 until at least 2024</a>, meaning that even fully custom Drupal 7 code — with very minor modifications — <i>should</i> work in the Backdrop CMS ecosystem. A large selection of widely used Drupal 7 modules are already available for Backdrop CMS, and more are on the way. And while backwards compatibility is a major selling point for Backdrop CMS, its architecture is forward thinking&nbsp;with the <a href="https://docs.backdropcms.org/converting-modules-from-drupal#classed-entities">introduction of classed entities</a> and an object-oriented approach to all of its new components and features.</p> <p>While the Backdrop CMS developer community isn’t particularly large, Drupal 7 and Backdrop CMS development skill sets are <i>virtually interchangeable</i> for the time being due to the almost identical API. There’s also considerable overlap on the Drupal 8/9 front due to Backdrop’s preference for object-oriented code in all of its newly added features. That means your existing Drupal developers can help you make the switch, and while the upgrade process isn’t exactly seamless <i>it’s definitely a far cry from a complete rebuild.</i></p> <p>Backdrop CMS also has its own security team, which — for now at least — works closely with the Drupal security team. Active development for the current version of Backdrop CMS is planned through 2024 according to their <a href="https://backdropcms.org/roadmap">Roadmap</a>, with the <i>next</i> version of Backdrop CMS promising an even easier upgrade path compared to the Drupal 7 to Backdrop CMS upgrade.</p> <h4><b>Cons </b></h4> <p>Backdrop CMS implements both Drupal 7 style procedural coding <i>and</i> Drupal 8/9 style object-oriented coding, which in theory means that it caters to a wide range of developers. In practice it’s hard to predict the future of any up-and-coming development community. That makes the outlook for long term support a little opaque, in that it’s hard to say just how many developers will be supporting Backdrop CMS and building new features for it down the road a couple of years.</p> <p>Also, while Backdrop CMS absolutely prioritizes backwards compatibility with Drupal 7, a greater number of contributed and custom modules in your existing site could complicate the upgrade process. Simpler Drupal 7 sites with fewer contributed and custom modules would probably encounter a <i>low </i>effort to complete the upgrade, while a greater number of contributed and custom modules are likely to see a <i>medium</i> effort as some of those modules may need to be converted.</p> <hr /> <h3>Drupal 7 Vender Extended Support: Don’t move a muscle</h3> <p><a href="https://www.drupal.org/project/d7es">Drupal 7 ES</a> is the <i>do nothing for now</i> option. A small collection of approved and vetted vendors will be providing security updates and / or critical patches for Drupal 7 core and contributed modules following a variety of vendor-specific plans.</p> <ul> <li>Cost: Low</li> <li>Build Time: None</li> <li>Longevity: Low / Medium</li> <li>Support: Medium</li> </ul> <h4><b>Pros </b></h4> <p>The biggest plus here is simple: <i>No further action is required at this time.</i> If you’re planning to work with a vendor that provides extended support for Drupal 7, you won’t need to take any action to protect your website from aging software until Drupal 7 EOL in 2022. At that point, you’ll need to plan on a flat or adjustable monthly fee through the end of 2025 — or possibly beyond. This could mean avoiding major&nbsp;financial decisions regarding your digital strategy for at least a few years, and all at a cost that (depending on the size of your organization / website) is probably a <i>fraction of the cost</i> of a software upgrade.</p> <p>With the Drupal 7 EOL recently extended until November of 2022, many of these Vender Extended Support plans haven’t fully materialized — so details are still forthcoming. Agencies like <a href="https://quo.tag1consulting.com/">Tag1Quo</a> or <a href="https://www.mydropwizard.com/">MyDropWizard</a> advertise services from a surprising $15 / month to $1250 / month for a range of beyond EOL Drupal support plans. <a href="https://www.acquia.com/">Acquia</a> and <a href="https://www.lullabot.com/">Lullabot</a> are also named by Drupal.org as ES vendors — but without any specifics about pricing or support levels. While the picture isn’t entirely clear yet, availability of an affordable ES plan is virtually guaranteed by 2022.</p> <h4><b>Cons </b></h4> <p>Drupal 7 Vendor Extended Support may protect you against vulnerabilities and exploits discovered after Drupal 7 EOL, but community support will be all but dead by that time. That means no new features or modules will be released and the pool of Drupal 7 developers will be rapidly drying up. Unless you have an in-house development team, you can plan on your website coming to a standstill in terms of new features.</p> <p>The amount of contributed and custom modules your site implements has an impact here, too. The greater number of custom and contributed modules, the greater you can expect the effort to be in supporting those modules beyond EOL.</p> <p>Another concern with Drupal 7 ES is PHP 7 end-of-life. Once PHP 7 (the language Drupal 7 is largely built on) is <a href="https://www.php.net/supported-versions.php">no longer supported towards the end of 2022</a>, you can expect the security of your Drupal 7 site to rapidly degrade. Updating Drupal 7 to be compliant with the newer, more secure PHP 8 is doable — but could&nbsp;be a difficult process.</p> <p>Finally, it’s likely that you will still need to consider upgrading to a supported platform in the future if your website will need to change and adapt in the coming years. You can expect this process to become <i>more challenging</i> as time goes on and the gap between your existing website and modern platforms grows ever larger.</p> <hr /> <h3>Taking the first step</h3> <p>There are other options, too. Moving from Drupal 7 to another platform entirely (WordPress?) could make the most sense depending on the complexities of your website. Moving to a less robust CMS could be nominally more cost effective than an upgrade to Drupal 9, but it also bakes in some hard limitations to what your website will be able to do.</p> <p>If you haven’t already, take a minute to register for Joel’s free webinar coming June 23rd. He'll be walking through a few of these options and more.</p> <div style="padding: 30px; border: 1px solid #e5e5e5;"> <p style="margin-bottom: 0;">Webinar, June 23rd:<br /> <strong>Options for upgrading your Drupal 7 or Drupal 8 site</strong></p> <p style="margin-top: 10px;"><a class="button" href="https://atendesigngroup.com/webinar/options-upgrading-your-drupal-7-or-drupal-8-site">Register Now</a></p> </div> <p>A lot of organizations are beginning to evaluate options for their Drupal 7 sites. The best way forward depends largely on your goals as an organization, your ambitions for your digital presence, and the amount of time and effort you’re willing to invest. We’d love to consider your questions or learn more about the specific challenges you’re facing as you sort through your options. <a href="/contact">Get in touch today with your questions about upgrade paths from Drupal 7.</a></p> <!-- google doc id: 11gzt_OpgNACGfM3EAfCPkjzysxeJPeZzfU3sAq0DLmY --></div> <a href="/about/eric-toupin" hreflang="en">Eric Toupin</a> Tue, 15 Jun 2021 20:14:04 +0000 Eric Toupin 10271 at https://atendesigngroup.com https://atendesigngroup.com/articles/upgrade-paths-your-drupal-7-website#comments Mapping Google Groups to Drupal user roles with Google Authentication https://atendesigngroup.com/articles/mapping-google-groups-drupal-user-roles-google-authentication <span>Mapping Google Groups to Drupal user roles with Google Authentication</span> <figure> <picture> <source srcset="/sites/default/files/2021-06/GOOGLE_AUTHORING_05.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-06/GOOGLE_AUTHORING_05.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-06/GOOGLE_AUTHORING_05.jpg?itok=Ht5nxez2 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-06/GOOGLE_AUTHORING_05.jpg?itok=dLR2zXdz 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-06/GOOGLE_AUTHORING_05.jpg?itok=dLR2zXdz 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-06/GOOGLE_AUTHORING_05.jpg?itok=dLR2zXdz 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-06/GOOGLE_AUTHORING_05.jpg?itok=3WYlw6MM 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-06/GOOGLE_AUTHORING_05.jpg?itok=3WYlw6MM 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-06/GOOGLE_AUTHORING_05.jpg" alt="" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/jennifer-dust" lang="" about="/user/jennifer-dust" typeof="schema:Person" property="schema:name" datatype="">jennifer</a></span> <span>Thu, 06/03/2021 - 13:19</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p>Mapping Google Groups to Drupal user roles with Google Authentication <i>seemed</i> like a straightforward task, but there were a few wrinkles in the process that I didn’t expect. We stumbled into the need for mapping groups to roles while performing a routine Drupal core upgrade from 8.8.5 to 8.9.13. Complications with the existing LDAP authentication’s upgrade path — and conversations with the client — steered us towards replacing LDAP with Google Authentication.</p> <p>Overall the idea is simple enough: when a user signs into your Drupal site using Google Authentication, their membership(s) to various Google Groups should map to some existing Drupal user roles. This way, users in your organization who are already affiliated with various groups can be automatically assigned appropriate user permissions in your Drupal site for editing or accessing content, approving comments, re-arranging homepage promotions or whatever the case may be.</p> <p>The majority of the functionality is established with just a few downloads, installations, and configurations. Our custom functionality relies on the <a href="https://www.drupal.org/project/social_api">Social API</a>, <a href="https://www.drupal.org/project/social_auth">Social Auth</a>, and <a href="https://www.drupal.org/project/social_auth_google/">Social Auth Google</a> modules, as well as the <a href="https://github.com/googleapis/google-api-php-client">Google APIs Client Library for PHP</a>. The first step is to install and enable the above mentioned modules and library. Then comes a little insider info on configuring the necessary <a href="https://cloud.google.com/iam/docs/service-accounts">Google Service Account</a>, and finally a custom implementation of the <i>EventSubscriberInterface</i> that reacts to authentication via Social Auth to read from Google Groups memberships. Our custom code is packaged into a <a href="https://github.com/AtenDesignGroup/demo_google_auth_subscriber">simple demo module you can download</a> to get all of this working for yourself.</p> <p>There are a lot of moving parts for something you’d expect to be a simple task, but in the end it’s a relatively quick setup if you know what to expect.</p> <h3>The devil’s in the details: Google Service Account configuration</h3> <p>This bit is pretty counterintuitive: Google users that authenticate via Google Authentication <i>don’t actually have read access to their own groups</i>. Weird, right? Where are you supposed to pull group information if the accounts can’t read their own group data from the API?</p> <p>The answer is a Google Service Account. A properly configured Google Service Account can have read access to both users and their group relationship information, which allows it to act as a go-between during authentication to pull Google Group data and map it to Drupal roles. Configuring your Google Service Account isn’t self-explanatory, but luckily <a href="https://atendesigngroup.com/about/joel-steidl">Joel Steidl</a> put together the following screencast to walk you through it. Once you’ve set up the Google Service Account, you’ll just need to create and download a JSON key file to include in the code we’ll walk through further below.</p> <p><div data-embed-button="media_browser" data-entity-embed-display="view_mode:media.embedded" data-entity-type="media" data-entity-uuid="397f3e25-e458-4220-aa12-129d2e4ef457" data-langcode="en" class="embedded-entity"><figure> <div class="field field--name-field-media-video-embed-field field--type-video-embed-field field--label-hidden field__item"><div class="video-embed-field-provider-youtube video-embed-field-responsive-video"><iframe width="854" height="480" frameborder="0" allowfullscreen="allowfullscreen" src="https://www.youtube.com/embed/LmVUW6kYs3Y?autoplay=1&amp;start=0&amp;rel=0"></iframe> </div> </div> </figure> </div> </p> <p>Now that your Google Service Account is properly configured and your JSON key file has been safely stored away, it’s time to tie everything together with a few blocks of code that do the actual role assigning.</p> <h3>Role assignment during Google Authentication: The who’s who handshake</h3> <p>Once you’ve configured your Google Service Account and downloaded your JSON key file, you need code that reacts to Social Auth events (like a login via Google Authentication) to trigger the role mapping functionality. I’ve added some example code below that's part of <a href="https://github.com/AtenDesignGroup/demo_google_auth_subscriber">a demo module</a> that should get you 95% of the way there, although in our demo the role mapping itself — which Google Group becomes which Drupal role — is hard coded.</p> <p>If you <a href="https://github.com/AtenDesignGroup/demo_google_auth_subscriber">download my demo module</a> you’ll see I’m reacting to both the USER_CREATED and USER_LOGIN events provided by the Social Auth module’s <i>getSubscribedEvents</i> method. This way we can reevaluate Google Group to Drupal role mappings each time a user logs in to make sure their roles and permissions stay up to date. You’ll also see that I’m verifying the user’s email address to make sure it falls within our expectations (user@your_domain.com), which you can modify or remove depending on your needs — but you want to make sure that your Google Service Account will have the appropriate access to this user’s group info.</p> <p>The magic happens in the <i>determineRoles</i> method, where the JSON key file and the Google Service Account you setup previously will come into play. You’ll need to modify the <i>getSecretsFile</i> method to return your JSON key file, then change the <i>$user_to_impersonate</i> variable to the email address that you granted the <i>Groups Reader</i> role as discussed in Joel’s screencast. Finally, you'll need to update the <em>$roleAssignment</em> array with actual Google Group names and Drupal roles.</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #009933; font-style: italic;">/** * When a user logs in verify their Google assigned group &amp; set permissions * * @param \Drupal\user\UserInterface $givenUser * The passed in user object. * */</span> <span style="color: #000000; font-weight: bold;">protected</span> <span style="color: #000000; font-weight: bold;">function</span> determineRoles<span style="color: #009900;">(</span><span style="color: #000088;">$givenUser</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #000088;">$KEY_FILE_LOCATION</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSecretsFile</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>   <span style="color: #666666; font-style: italic;">// Only run if we have the secrets file</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #000088;">$KEY_FILE_LOCATION</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #666666; font-style: italic;">// 1. Admin SDK API must get enabled for relevant project in Dev Console.</span> <span style="color: #666666; font-style: italic;">// 2. Service user must get created under relevant project and based on a user with</span> <span style="color: #666666; font-style: italic;">// 3. User must have Groups Reader permission.</span> <span style="color: #666666; font-style: italic;">// 4. Scope must get added to Sitewide Delegation.</span> <span style="color: #000088;">$user_to_impersonate</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'example_google_account@your_domain.com'</span><span style="color: #339933;">;</span> <span style="color: #000088;">$client</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Google_Client<span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$client</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setAuthConfig</span><span style="color: #009900;">(</span><span style="color: #000088;">$KEY_FILE_LOCATION</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$client</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setApplicationName</span><span style="color: #009900;">(</span><span style="color: #0000ff;">'Get a Users Groups'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$client</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setSubject</span><span style="color: #009900;">(</span><span style="color: #000088;">$user_to_impersonate</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$client</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setScopes</span><span style="color: #009900;">(</span><span style="color: #009900;">[</span>Google_Service_Directory<span style="color: #339933;">::</span><span style="color: #004000;">ADMIN_DIRECTORY_GROUP_READONLY</span><span style="color: #009900;">]</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$groups</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Google_Service_Directory<span style="color: #009900;">(</span><span style="color: #000088;">$client</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>   <span style="color: #000088;">$params</span> <span style="color: #339933;">=</span> <span style="color: #009900;">[</span> <span style="color: #0000ff;">'userKey'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$givenUser</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getEmail</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">,</span> <span style="color: #009900;">]</span><span style="color: #339933;">;</span> <span style="color: #000088;">$results</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$groups</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">groups</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">listGroups</span><span style="color: #009900;">(</span><span style="color: #000088;">$params</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>   <span style="color: #666666; font-style: italic;">// Hold Map for roles based on Google Groups</span> <span style="color: #000088;">$roleAssignment</span> <span style="color: #339933;">=</span> <span style="color: #009900;">[</span> <span style="color: #0000ff;">"Author Group Name"</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">"author"</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">"Editor Group Name"</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">"editor"</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">"Publisher Group Name"</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">"publisher"</span><span style="color: #339933;">,</span> <span style="color: #009900;">]</span><span style="color: #339933;">;</span>   <span style="color: #666666; font-style: italic;">// Loop through the user's groups an add approved roles to array</span> <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">(</span><span style="color: #000088;">$results</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'groups'</span><span style="color: #009900;">]</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$result</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #000088;">$name</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$result</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'name'</span><span style="color: #009900;">]</span><span style="color: #339933;">;</span>   <span style="color: #666666; font-style: italic;">//Assign roles based on what was determined</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><a href="http://www.php.net/array_key_exists"><span style="color: #990000;">array_key_exists</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$name</span><span style="color: #339933;">,</span> <span style="color: #000088;">$roleAssignment</span><span style="color: #009900;">)</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #000088;">$givenUser</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">addRole</span><span style="color: #009900;">(</span><span style="color: #000088;">$roleAssignment</span><span style="color: #009900;">[</span><span style="color: #000088;">$name</span><span style="color: #009900;">]</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$givenUser</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span> <span style="color: #009900;">}</span> <span style="color: #009900;">}</span> <span style="color: #009900;">}</span></pre></div></pre> <p>That’s it! With your modified demo module enabled, your users should get their roles automatically assigned on login. We were pretty excited to get Google Groups mapping to Drupal roles, so much so that my colleague <a href="https://atendesigngroup.com/about/matthew-luzitano">Matthew Luzitano</a> is considering packaging this functionality (and GUI configurations!) into a Drupal module. So if you’re not in any rush and don’t feel like browsing our demo code, you can always wait until that happens.</p> <p>Do you have a different approach to mapping Google Groups to Drupal roles? We’d love to hear about it! Let us know in the comments below.</p> <!-- google doc id: 1WDvGQabkStHVVnqycoll6IhNrfasTR10V9SoqtDv4j8 --></div> <a href="/about/jennifer-dust" hreflang="en">Jennifer Dust</a> Thu, 03 Jun 2021 19:19:10 +0000 jennifer 10268 at https://atendesigngroup.com https://atendesigngroup.com/articles/mapping-google-groups-drupal-user-roles-google-authentication#comments Codeless, Drag-and-Drop Editing in Drupal https://atendesigngroup.com/webinar/codeless-drag-and-drop-editing-in-drupal <span>Codeless, Drag-and-Drop Editing in Drupal </span> <article data-history-node-id="10170" role="article" about="/about/justin-toupin" class="js--block-link profile profile--compact" data-href="/about/justin-toupin" > <div class="profile__image"> <figure> <picture> <source srcset="/sites/default/files/justin-toupin.png 1x" media="(min-width: 1860px)" type="image/png"/> <source srcset="/sites/default/files/justin-toupin.png 1x" media="(min-width: 1540px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_large/public/justin-toupin.png?itok=NianpUq1 1x" media="(min-width: 1265px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/justin-toupin.png?itok=bArgN4qT 1x" media="(min-width: 1024px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/justin-toupin.png?itok=bArgN4qT 1x" media="(min-width: 768px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/justin-toupin.png?itok=bArgN4qT 1x" media="(min-width: 600px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_small/public/justin-toupin.png?itok=VgUnrd4B 1x" media="(min-width: 500px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_small/public/justin-toupin.png?itok=VgUnrd4B 1x" media="(min-width: 0)" type="image/png"/> <img src="/sites/default/files/justin-toupin.png" alt="" typeof="foaf:Image" /> </picture> </figure> </div> <div class="profile__content clearfix"> <h3 class="profile__title"> <a href="/about/justin-toupin" class="profile__title-link">Justin Toupin</a> </h3> <div class="profile__job-title">CEO</div> </div> </article> <span><a title="View user profile." href="/user/1558" lang="" about="/user/1558" typeof="schema:Person" property="schema:name" datatype="">jenna</a></span> <span>Fri, 04/30/2021 - 14:28</span> <a href="/media/3486" hreflang="und">Justin Toupin</a> <div class="field field--name-field-zoom-webinar-agenda field--type-string-long field--label-hidden field__item">Codeless, Drag-and-Drop Editing in Drupal</div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>We’ve been working with Drupal for a long time: since way back in 2006, when Drupal had just reached version 4.6.&nbsp;The platform has come a long way, and in particular Drupal 8 and 9 have been huge jumps forward.&nbsp;<br /> <br /> Meanwhile, so has the rest of the publishing industry. Building content online has never been easier, with products like Squarespace, Wix, and even WordPress defining the new standard for easy-to-use publishing&nbsp;tools.<br /> <br /> We’ve been working on a suite of tools that combines the flexibility and robust capabilities of Drupal 8 and 9 with the simple content management&nbsp;experience that marketing and&nbsp;communications&nbsp;teams need and expect.&nbsp;<br /> <br /> In this webinar, we’ll walk through the components of Aten’s modern, easy-to-use, drag-and-drop authoring experience&nbsp;that empowers teams to publish rich, beautiful content in Drupal, quickly.</p></div> <div class="field field--name-field-wrap-up field--type-text-with-summary field--label-hidden field__item"><p style="line-height:1.38"><br style="font-style:normal; font-weight:normal; text-align:start; white-space:normal; text-decoration:none; color:#000000" /> &nbsp;</p></div> <div class="field field--name-field-date field--type-daterange field--label-above"> <div class="field__label">Date</div> <div class="field__item"><time datetime="2021-05-26T18:00:00Z">Wed, 05/26/2021 - 12:00</time> - <time datetime="2021-05-26T19:00:00Z">Wed, 05/26/2021 - 13:00</time> </div> </div> <div class="field field--name-field-zoom-webinar-join-url field--type-string field--label-hidden field__item">https://atendesigngroup.zoom.us/j/96420540632</div> <figure> <div class="field field--name-field-media-video-embed-field field--type-video-embed-field field--label-hidden field__item"><div class="video-embed-field-provider-youtube video-embed-field-responsive-video"><iframe width="854" height="480" frameborder="0" allowfullscreen="allowfullscreen" src="https://www.youtube.com/embed/C071U9eNWG0?autoplay=1&amp;start=13&amp;rel=0"></iframe> </div> </div> </figure> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <a href="/articles/authoring-experience" class="tag" >Authoring Experience</a> Fri, 30 Apr 2021 20:28:06 +0000 jenna 10256 at https://atendesigngroup.com Cascading permissions for complex organizations in Drupal 8 https://atendesigngroup.com/articles/cascading-permissions-complex-organizations-drupal-8 <span>Cascading permissions for complex organizations in Drupal 8</span> <figure> <picture> <source srcset="/sites/default/files/2021-04/ENTITY_ACCESS_02%20%281%29.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-04/ENTITY_ACCESS_02%20%281%29.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-04/ENTITY_ACCESS_02%20%281%29.jpg?itok=pCwTHzsY 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-04/ENTITY_ACCESS_02%20%281%29.jpg?itok=7vjjfk6w 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-04/ENTITY_ACCESS_02%20%281%29.jpg?itok=7vjjfk6w 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-04/ENTITY_ACCESS_02%20%281%29.jpg?itok=7vjjfk6w 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-04/ENTITY_ACCESS_02%20%281%29.jpg?itok=f_W11lfe 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-04/ENTITY_ACCESS_02%20%281%29.jpg?itok=f_W11lfe 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-04/ENTITY_ACCESS_02%20%281%29.jpg" alt="" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/jordan-graham" lang="" about="/user/jordan-graham" typeof="schema:Person" property="schema:name" datatype="">Jordan Graham</a></span> <span>Fri, 04/23/2021 - 14:37</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p>Aten loves libraries. We’ve built a range of software solutions for libraries all over the country, including the John D. Rockefeller Jr. Library in Williamsburg, VA, the Denver Public Library in our own Denver county, the Richland Library in South Carolina, Nashville Public Library in Tennessee and the Reaching Across Illinois Library System (“<i>RAILS”</i>). It’s remarkable just how many commonalities libraries share when it comes to the features, tools, and considerations their websites need to better serve their users.</p> <p>Some of those similarities are a no-contest justification for building common, configurable library solutions — solutions like <a href="https://www.libraryintercept.com/">Intercept</a>: a reimagined, generalized approach to library event management, room and equipment reservation, and customer tracking. We co-developed <a href="https://www.drupal.org/project/intercept">Intercept for Drupal</a> with Richland Library, reused it with L2 / RAILS and actively maintain it in the hopes that it goes on serving libraries for years to come. But for all of the similarities we see between libraries and their digital needs, there are some key differences, too.</p> <h3>Complex permissions needs</h3> <p><a href="https://librarylearning.org/">Library Directory and Learning Calendar</a> (“L2”) — a project of RAILS — had unique permissions needs. Their staff needed custom view, edit, and delete permissions to website content managed at different organizational levels of the library system. Those organizational levels, structured hierarchically from Illinois State Library, to Regional Library System, through Catalog Consortium, Agency and finally Location, needed associated <i>cascading permissions</i> — i.e., permissions granted at a higher organizational level should <em>cascade</em> to associated content in lower organizational levels. An administrator for an Illinois State Library piece of content, for example, should be able to exercise their administrative permissions on Regional, Consortium, Agency, and Location content associated with that State Library content. An administrator for an Agency would have administrative permissions for that Agency’s Locations.</p> <p>Granular role based Drupal permissions wouldn’t cut it. That’s because with standard Drupal permissions, each user role can be assigned view, edit, and delete permissions to each content type (say any <em>Regional Library System Page</em>), but we needed to assign those permissions to the appropriate <i>instances</i> of a content type — like the specific Regional Library System Page that belongs to the west-central region, for example.</p> <p>There are plenty of contributed modules that start down the right path towards (for lack of a better term) <i>cascading permissions by content affiliation</i>, but they wouldn’t have gotten us all the way there. Both the <a href="https://www.drupal.org/project/group">Group</a> and <a href="https://www.drupal.org/project/permissions_by_term">Permissions by term</a> modules, for example, can be incredibly useful in similar situations. In this case, given the features and functionality contributed modules would introduce that we don’t need, plus the level of modification necessary to achieve our goals, we decided on a lightweight, custom solution.</p> <h3>Role and affiliation based custom permissions for L2</h3> <p>Permissions for L2 staff are established using a custom affiliation entity, which stores data about <i>the role a particular user has in relation to a specific piece of content.</i> The custom affiliation entity references a <i>user</i>, a <i>role</i> (a taxonomy term like <i>Admin</i>, <i>Manager</i>, or <i>Staff</i>, for example), and a <i>specific piece of content</i> (a node). A variety of other fields are established in the same affiliation entity in order to store additional metadata about the relationship like contact information, job title, job description, or other details.</p> <figure role="group" class="align-center"> <div alt="Custom affiliation entity field configuration screen, Drupal 8" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="87d8b838-4ed8-4fa4-93d5-baabdf151d4a" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2021-04/affiliation_entity_fields.png" alt="Custom affiliation entity field configuration screen, Drupal 8" title="Affiliation entity" typeof="foaf:Image" /> </div> <figcaption><em>Affiliation</em>, <em>Role / Access Group</em>, and <em>User</em> fields establish a permission relationship. Metadata fields (blurred here) can provide some arbitrary data about the relationship.</figcaption> </figure> <p>The custom affiliation entities are organized into their own bundles, one for each of the hierarchically structured organizational levels previously described: Illinois State Library, Regional Library System, Catalog Consortium, Agency, and finally Location. This way each individual type of affiliation can contain the metadata fields appropriate for its specific organizational level. Finally, there is an arbitrary Group affiliation which affiliates a user with a piece of content without granting the cascading permissions that accompany standard affiliations.</p> <figure role="group" class="align-center"> <div alt="Custom entity affiliation entity types, Drupal 8" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="e6275650-37b0-4ce5-b139-0b4145f26eec" data-langcode="en" title="Affiliation entity types" class="embedded-entity"> <img src="/sites/default/files/2021-04/affiliation_entity_types.png" alt="Custom entity affiliation entity types, Drupal 8" title="Affiliation entity types" typeof="foaf:Image" /> </div> <figcaption>Each organizational level is represented by its own custom affiliation entity type.</figcaption> </figure> <p>The content items (read <i>nodes</i>) whose permissions we’re controlling are organized along the same organizational levels: Illinois State Library, Regional Library System, etc. Each of these content types uses a unique entity reference field to establish a parent / child relationship along the organizational levels. Locations are associated with Agencies, Catalog Consortia, Regional Library Systems or directly with the Illinois State Library; Agencies are associated with other Agencies and / or Regional Library Systems; Catalog Consortia are associated with Regional Library Systems; and Regional Library Systems with the single parent Illinois State Library. It’s a complicated web!</p> <figure role="group" class="align-center"> <div alt="Cascading permissions diagram" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="54badc66-09e6-49b3-b68f-c080551730fc" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2021-04/aten_rails_cascading_permissions.png" alt="Cascading permissions diagram" title="Cascading permissions" typeof="foaf:Image" /> </div> <figcaption>Permissions granted on content at any one organizational level <em>cascade</em> to associated content in the lower levels.</figcaption> </figure> <p>Once the content for L2 was developed and properly structured with the appropriate parent / child relationships, granting a user specific view, edit, and delete permissions for a particular region of the structured content tree was simple. Simply create an affiliation that assigns the user a role in relation to content at a specific level of the organization, and voila! — the user gains access to that content and all of its children at each of the lower levels.</p> <h3>The permission grid: Tying it all together</h3> <p>One last element ties our whole permissions system together: a robust permissions map that associates view, edit, and delete permissions per custom defined <em>Role / Access Group</em> in relation to various entities. Unlike the unwieldy Drupal permissions grid that assigns roles to broadly defined permissions with the help of about a million radio buttons, our definitions can be static (think code, not GUI) and only have to deal with view, edit, and delete permissions for entities or entity fields. Each Role / Access Group has its view, edit and delete permissions defined per bundle or per specific bundle / field combination, resulting in very cleancut — and extremely granular — permission control.</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #009900;">[</span> <span style="color: #009900;">{</span> <span style="color: #0000ff;">"entity"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">"node"</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// Entity type we're granting access to</span> <span style="color: #0000ff;">"field"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">""</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// Field for this entity, left blank we're defining with entity level access</span> <span style="color: #0000ff;">"affiliation bundle"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">"location"</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// Affiliation entity that controls access, in this case the location affiliation type</span> <span style="color: #0000ff;">"target bundle"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">"location"</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// Entity bundle we're granting access to, in this case a location node</span> <span style="color: #0000ff;">"role_access_group"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">"location_manager"</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// Custom defined Role / Access Group that grants this permission</span> <span style="color: #0000ff;">"edit"</span><span style="color: #339933;">:</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// Edit permission</span> <span style="color: #0000ff;">"view"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">""</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// View permission</span> <span style="color: #0000ff;">"delete"</span><span style="color: #339933;">:</span> <span style="color: #0000ff;">""</span> <span style="color: #666666; font-style: italic;">// Delete permission</span> <span style="color: #009900;">}</span> <span style="color: #009900;">]</span></pre></div></pre> <p>Our “permissions grid” is made up of about 500 similar declarations in a single JSON file, which range through a variety of Roles / Access Groups, a couple of entity types, and tons of bundle / field combinations for some of the more complex field level permissions.</p> <p>Individual permissions grants are then handled through hook_ENTITY_TYPE_access() and hook_entity_field_access(), which use a a custom Service to load all of the requesting user’s affiliation entities, determine their role in relation to the content (node) in question, then find that role’s particular permissions using our custom JSON permissions map. Here’s an example for node access.</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #009933; font-style: italic;">/** * Determines if the operation is allowed for a node. * * @param object $relationship * The relationship object. * @param string $operation * The operation being attempted. * @param Drupal\node\Entity\Node $node * The node on which the operation is being attempted. * * @return bool * True if the operation is allowed. */</span> <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> nodePermission<span style="color: #009900;">(</span>object <span style="color: #000088;">$relationship</span><span style="color: #339933;">,</span> <span style="color: #000088;">$operation</span><span style="color: #339933;">,</span> Node <span style="color: #000088;">$node</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #666666; font-style: italic;">// Create an array of data from l2_access_permissions.json, each element</span> <span style="color: #666666; font-style: italic;">// of which is an array with elements matching $relationship object</span> <span style="color: #666666; font-style: italic;">// properties.</span> <span style="color: #000088;">$module_path</span> <span style="color: #339933;">=</span> drupal_get_path<span style="color: #009900;">(</span><span style="color: #0000ff;">'module'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'l2_access'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #000088;">$matrix</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/json_decode"><span style="color: #990000;">json_decode</span></a><span style="color: #009900;">(</span><a href="http://www.php.net/file_get_contents"><span style="color: #990000;">file_get_contents</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$module_path</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'/l2_access_permissions.json'</span><span style="color: #009900;">)</span><span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>   <span style="color: #666666; font-style: italic;">// Create an array matching the structure of $matrix elements to see if</span> <span style="color: #666666; font-style: italic;">// it matches any $matrix elements (which would mean that there might be</span> <span style="color: #666666; font-style: italic;">// a permission that allows this $operation.)</span> <span style="color: #000088;">$relationship_array</span> <span style="color: #339933;">=</span> <span style="color: #009900;">[</span> <span style="color: #0000ff;">'entity'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">'node'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'field'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'affiliation bundle'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$relationship</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">affiliation_bundle</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'target bundle'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$relationship</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">target_bundle</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'role_access_group'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$relationship</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">role_access_group</span><span style="color: #339933;">,</span> <span style="color: #009900;">]</span><span style="color: #339933;">;</span>   <span style="color: #666666; font-style: italic;">// Set the $relationship_array's 'edit' and 'view' elements based on the</span> <span style="color: #666666; font-style: italic;">// $operation's value.</span> <span style="color: #000088;">$operation</span> <span style="color: #339933;">=</span> <span style="color: #009900;">(</span><span style="color: #000088;">$operation</span> <span style="color: #339933;">==</span> <span style="color: #0000ff;">'update'</span><span style="color: #009900;">)</span> ? <span style="color: #0000ff;">'edit'</span> <span style="color: #339933;">:</span> <span style="color: #000088;">$operation</span><span style="color: #339933;">;</span> <span style="color: #000088;">$operations</span> <span style="color: #339933;">=</span> <span style="color: #009900;">[</span><span style="color: #0000ff;">'view'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'edit'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'delete'</span><span style="color: #009900;">]</span><span style="color: #339933;">;</span> <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">(</span><span style="color: #000088;">$operations</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$op</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #000088;">$relationship_array</span><span style="color: #009900;">[</span><span style="color: #000088;">$op</span><span style="color: #009900;">]</span> <span style="color: #339933;">=</span> <span style="color: #009900;">(</span><span style="color: #000088;">$op</span> <span style="color: #339933;">==</span> <span style="color: #000088;">$operation</span><span style="color: #009900;">)</span> ? <span style="color: #cc66cc;">1</span> <span style="color: #339933;">:</span> <span style="color: #0000ff;">""</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span>   <span style="color: #666666; font-style: italic;">// Handy here that array_search() can test whether an array is an element</span> <span style="color: #666666; font-style: italic;">// in another array. Here: is $relationship_array an element of $matrix?</span> <span style="color: #000088;">$match</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array_search"><span style="color: #990000;">array_search</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$relationship_array</span><span style="color: #339933;">,</span> <span style="color: #000088;">$matrix</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #339933;">!</span><span style="color: #000088;">$match</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #b1b100;">return</span> <span style="color: #009900; font-weight: bold;">FALSE</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span>   <span style="color: #666666; font-style: italic;">// Found a match. Does it allow access?</span> <span style="color: #b1b100;">switch</span> <span style="color: #009900;">(</span><span style="color: #000088;">$operation</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #b1b100;">case</span> <span style="color: #0000ff;">'edit'</span><span style="color: #339933;">:</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #000088;">$matrix</span><span style="color: #009900;">[</span><span style="color: #000088;">$match</span><span style="color: #009900;">]</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'edit'</span><span style="color: #009900;">]</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #b1b100;">return</span> <span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span> <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span>   <span style="color: #b1b100;">case</span> <span style="color: #0000ff;">'update'</span><span style="color: #339933;">:</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #000088;">$matrix</span><span style="color: #009900;">[</span><span style="color: #000088;">$match</span><span style="color: #009900;">]</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'edit'</span><span style="color: #009900;">]</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #b1b100;">return</span> <span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span> <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span>   <span style="color: #b1b100;">case</span> <span style="color: #0000ff;">'view'</span><span style="color: #339933;">:</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #000088;">$matrix</span><span style="color: #009900;">[</span><span style="color: #000088;">$match</span><span style="color: #009900;">]</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'view'</span><span style="color: #009900;">]</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span> <span style="color: #b1b100;">return</span> <span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span> <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span>   <span style="color: #666666; font-style: italic;">// If we're here, this $relationship doesn't provide $operation access.</span> <span style="color: #b1b100;">return</span> <span style="color: #009900; font-weight: bold;">FALSE</span><span style="color: #339933;">;</span> <span style="color: #009900;">}</span></pre></div></pre> <p>The end result is powerful and flexible. Our JSON permissions map tells us which Roles / Access Groups have which permissions per entity or entity / field combination. The custom affiliation entities grant users a specific Role / Access Group in relation to a specific piece of content, and that content’s entity references to other entities allow the permissions to cascade to entities in lower organizational levels.</p> <p>That may sound like a lot, but it’s surprisingly simple. The solution boils down to a handful of entity access hook implementations that use a custom service to lookup the current user’s permissions by affiliation via a JSON permissions map. The total footprint sits at around 1000 lines of code — not counting the permissions map itself — and flexibly manages thousands of users’ permissions across thousands of complex, hierarchical content relationships down to the field level. Pretty neat.</p> <!-- google doc id: 1mkAxX8EHNcRSKVk87wW4tpgOEe3LRg7p6BCVWmNHAPQ --></div> <a href="/about/jordan-graham" hreflang="en">Jordan Graham</a> Fri, 23 Apr 2021 20:37:56 +0000 Jordan Graham 10254 at https://atendesigngroup.com https://atendesigngroup.com/articles/cascading-permissions-complex-organizations-drupal-8#comments The body field is dead https://atendesigngroup.com/articles/body-field-dead <span>The body field is dead</span> <figure> <picture> <source srcset="/sites/default/files/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg?itok=TbgXmbKT 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg?itok=Fu66Vu4d 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg?itok=Fu66Vu4d 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg?itok=Fu66Vu4d 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg?itok=O0f_qjbU 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg?itok=O0f_qjbU 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-04/AUTHORING_EXPERIENCE_TYPESETTING_05.jpg" alt="" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/james-nettik" lang="" about="/user/james-nettik" typeof="schema:Person" property="schema:name" datatype="">jnettik</a></span> <span>Thu, 04/15/2021 - 09:56</span> <a href="/articles/authoring-experience" class="tag" >Authoring Experience</a> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p>Content authoring on the web has evolved. Compelling web content uses a variety of multimedia elements to engage users, tell stories, build brands, and share new ideas. Images, video clips, slideshows, static or parallax backgrounds, block quotes and text pullouts — more than ever, content creators need a tool that embraces an evolving medium and keeps pace with the author’s creativity. We believe <a href="https://atendesigngroup.com/articles/expressive-authoring-layout-paragraphs">Layout Paragraphs</a> is that tool. But first, let’s all agree that the body field is dead.</p> <h3>What is the body field</h3> <p>Web editors and content authors who have been around over the last decade of digital media are intimately familiar with the body field. Many popular content management systems (looking at you, Drupal and Wordpress) feature a standard “post” or “content” type — like page, article, or blog post — right out of the box. These content types often have a couple of fields to fill out before you can publish your post, things like title, tags, friendly URL / slug, and of course <i>body</i>.</p> <p>The body field, traditionally, is where all your content goes. In the early days of blogging and web publishing that was likely to be all or mostly text, but with time came images, videos, and eventually an array of other multimedia elements.<i> And the body field adapted.</i> Tokens became standard for plenty of web editors — cryptic chunks of text like <i>[[nid:376 align:right]]</i> that would be auto-magically replaced with other elements once you click Publish. WYSIWYG editors (What You See Is What You Get) started shipping with <i>Insert image</i> and <i>Insert video</i> buttons, and began including a variety of tools for encapsulating, aligning, and positioning lengths of text or various media elements. And while these accommodations started to connect content creators with the boundless possibilities of multimedia authoring, they were (and are) clumsy and unpredictable.</p> <h3>Structured content: Reduce, reuse, recycle</h3> <p>One major problem with all of these innovations is that once you click Publish, all that complex content <i>still ends up in the body field</i> — that is, saved into your database as one giant clump of complicated text, tokens, style codes, etc. If you’d like to publish another page with a similar look &amp; feel, get ready to re-inject all of your tokens or go through the same WYSIWYG click-a-thon to reestablish your styles and layout. Thinking of producing a list of all the images used in your posts? Good luck! With all content mushed together into a single field, your site doesn’t “know” the difference between a paragraph of text, an image, or a video.</p> <p>A structured approach to content organizes each individual element — a pane of text, an image, slideshow, video, or a “donate now” banner — as its own self-contained entity which, ideally, could then be placed within flexibly configured regions on a page. Creating other, similar pages then becomes as simple as swapping out individual elements or shifting them around between regions of a page. And libraries of reusable content elements become the norm — so that you’re now picking your images, videos, donate banners and more from lists of existing content or existing content styles, instead of trying to hunt down how you did something similar the last time.</p> <p>Structuring your content into individual, reusable elements drives a lot of collateral benefits:</p> <ul> <li><b>Ease of content creation.</b> Authoring with a consistent set of components lets you focus more on content, hierarchy, and intent — instead of trying to remember how you got a video in there at all the last time.</li> <li><b>Styling and restyling.</b> Using discrete elements of content means that your HTML markup and CSS remains consistent across all of your various pages. When the time comes to update the look &amp; feel of your website, changes made on one article page will “just work” on the rest of your articles and all of their elements.</li> <li><b>Content migration.</b> The day might arrive when you consider migrating your content to a newer platform or different software. Structured content makes that a snap: Each image, video, slideshow, or paragraph of text gets individually ported to its new home on the new platform. On the contrary, migrating a mess of markup, tokens, and style codes stored in a single body field means writing complex, custom code to recognize those items and deal with them appropriately — not a simple task.</li> <li><b>Syndication.</b> Want to feature your content in a collection via RSS or deploy it to an app with XML? With structured content apps and other websites can consume just the elements they want of your content (maybe the first paragraph, a title, an image, or a link) then display those elements appropriately according to their own styles — rather than just grabbing a few hundred lines of a single, messy body field and making do.</li> <li><b>Libraries, lists, and cross-promotion.</b> Want to see all the slideshows that appear in articles with a specific tag? Maybe a list of pull-quotes from your most recent blog posts? Structured content creates a world of opportunity around libraries, lists and cross-promotions.</li> </ul> <p>Structuring content facilitates reuse, and it positions your content to go on living in a variety of platforms or a variety of presentations — long after you first click the Publish button.</p> <h3>The two faces of web content</h3> <p>Structuring content is an essential part of beautiful, intuitive digital authoring, but it’s only one side of the coin. The other side is putting an end to the two faces of your web content. Excepting some of the advances of modern WYSIWYG editors, web content has always had two very different faces: <em>View</em> and <em>Edit</em>.</p> <p>The way content is <em>edited</em> and the way it’s <em>presented</em> are often strikingly different. Especially if you’re still holding on to “one giant body field” innovations like custom tokens, BBCode, Markdown, or short codes, it can take a handful of <em>Preview</em> to <em>Edit</em> to <em>Preview</em> roundtrips to get your content exactly how you want it. Even if you’ve made the move to structured content, a majority of web-based editors are marked by a dramatic difference between the “back end” and the “front end” — just one more digital convention getting between inspired authors and publishing beautiful content easily.</p> <h3>The king is dead, long live the king</h3> <p>The body field is dead. It’s taken center stage in publishing digital content for long enough. Plenty of web products (<a href="https://medium.com/">Medium</a> and <a href="https://www.notion.so/">Notion</a>, to name a couple) have driven nails in that coffin for publishers creating content on those specific platforms. But what about content creators and web editors working within their organizations’ websites, in custom web applications where content authoring is just one of several important features? What about you, and your organization’s website? <em>What’s next for your web application?</em></p> <p>For Drupal websites, we think Layout Paragraphs is what’s next. It was first released to the Drupal community nearly a year ago, and has been the beneficiary of ambitious development ever since.</p> <p>The Layout Paragraphs module makes it dead simple for authors to create robust, multimedia content from a library of available components. It offers intuitive drag-and-drop controls for managing content flow and layout, and with appropriate styling can bring the two faces of web content — <em>View</em> and <em>Edit</em> — closer than ever before. We built Layout Paragraphs to embrace the future of multimedia content authoring and to solve the problems we watch clients work through every day.</p> <p>You can watch a short, <a href="https://atendesigngroup.com/articles/expressive-authoring-layout-paragraphs">two-minute demo of Layout Paragraphs</a> here, or follow the instructions in that post to see it in action for yourself.</p> <!-- google doc id: 1tYNNuOS56RvD2_m4n1VKh6uWSS3lv07dnnPriKu_J3I --></div> <a href="/about/james-nettik" hreflang="en">James Nettik</a> Thu, 15 Apr 2021 15:56:20 +0000 jnettik 10251 at https://atendesigngroup.com https://atendesigngroup.com/articles/body-field-dead#comments Drupal 7 to Drupal 8, 9 and beyond: Your last major upgrade? https://atendesigngroup.com/articles/drupal-7-drupal-8-9-and-beyond-your-last-major-upgrade <span>Drupal 7 to Drupal 8, 9 and beyond: Your last major upgrade?</span> <figure> <picture> <source srcset="/sites/default/files/2021-03/DRUPAL_UPGRADES_05.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-03/DRUPAL_UPGRADES_05.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-03/DRUPAL_UPGRADES_05.jpg?itok=ovlpIBcj 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-03/DRUPAL_UPGRADES_05.jpg?itok=zRCf3XEW 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-03/DRUPAL_UPGRADES_05.jpg?itok=zRCf3XEW 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-03/DRUPAL_UPGRADES_05.jpg?itok=zRCf3XEW 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-03/DRUPAL_UPGRADES_05.jpg?itok=iR0-tnMo 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-03/DRUPAL_UPGRADES_05.jpg?itok=iR0-tnMo 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-03/DRUPAL_UPGRADES_05.jpg" alt="" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/joel-steidl" lang="" about="/user/joel-steidl" typeof="schema:Person" property="schema:name" datatype="">Joel Steidl</a></span> <span>Thu, 03/11/2021 - 11:41</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p>Upgrading from Drupal 7 to Drupal 8 is a major project (a full rebuild in most cases) that can rival or even top the original cost of application development. Upgrading from Drupal 8 to 9 and beyond, in comparison, requires just a tiny fraction of that effort. Here’s a look at why — and why you should take the leap from Drupal 7 to Drupal 8 and 9.</p> <p>The upgrade from Drupal 7 to Drupal 8 was a major theme for plenty of our clients over the last few years. Drupal 8 — and versions beyond — boast a wide range of benefits that stem from a more modern architecture, but one of the biggest wins in my mind is the advent of <a href="https://semver.org/">semantic versioning</a> (for Drupal) and a <a href="https://dri.es/making-drupal-upgrades-easy-forever">renewed commitment to making Drupal upgrades easy forever</a>. Moving to Drupal 8 could be your last major upgrade.</p> <h3>The ghost of Drupal past</h3> <p>The move from Drupal 7 to Drupal 8 isn’t easy. While it’s often still touted as an “upgrade process” the reality is that it boils down to a complete rewrite of the codebase <i>and a content migration</i>. We’ve worked on more than a handful of Drupal 7 to Drupal 8 upgrades in the last years, and the majority constitute a “six months or more” sized project.</p> <p>One reason for that is Drupal’s new <a href="https://symfony.com/">Symfony</a> reliant architecture that leans far closer to the tenets of object oriented programming (OOP) versus the more procedural approach used in previous Drupal versions — a significant technical change which in most cases means<i> Drupal 7 code simply won’t work in Drupal 8.</i> Add to that a completely revamped templating system and you get a similar obstacle on the front-end: <i>Drupal 7 templates &amp; templating systems won’t work in Drupal 8</i>. Those major changes along with a potentially long list of complications with Drupal 8 data migrations is likely to land your Drupal 7 to Drupal 8 upgrade squarely in “rebuild the application” territory.</p> <p><i>The good news is that it’s the last time.</i></p> <h3>Drupal 8 and beyond: Your last major upgrade</h3> <p>Drupal 8, 9 and beyond promise a continued investment in modern architecture, incremental major feature releases, greater stability and sustainability, and a much larger network of invested developers — among lots of other perks. Perhaps the best news to come along with the new Drupal architecture is that large-scale, overhaul-style major version upgrades will be a thing of the past. And we’re beginning to see that now as we begin moving Drupal 8 sites to Drupal 9.</p> <h4>Clearing the road ahead: A commitment to easy upgrade paths</h4> <p>With Drupal 8 comes the commitment to semantic versioning. Semantic versioning isn’t just a naming convention for software versions, it’s a descriptive norm that guides how new versions of code should be written and establishes strict backwards compatibility requirements. The upshot for end users is significant but manageable incremental changes between major versions (say 8.x and 9.x) which aim towards seamless major version upgrades. Major versions can no longer make dramatic jumps (like Drupal 8’s move to OOP or the Twig templating system) which necessitate major code rewrites, but instead make small, reasonable changes between minor versions.</p> <p>With the new approach to versioning developers get a variety of automated tools that ease the shift&nbsp;between minor and major versions. <a href="https://www.drupal.org/docs/updating-drupal/how-to-prepare-your-drupal-7-or-8-site-for-drupal-9/deprecation-checking-and">Deprecation checking</a> means developers get notices in their code editors when parts of the existing codebase have been marked as deprecated, i.e., queued for change or removal in an upcoming major version. Automated tools like <a href="https://www.drupal.org/project/upgrade_status">Upgrade Status</a> or <a href="https://github.com/palantirnet/drupal-rector">Drupal Rector</a> and its Drupal front-end <a href="https://www.drupal.org/project/upgrade_rector">Upgrade Rector</a> can provide detailed upgrade status information and even automatically update code between major versions. All in all, upgrade paths are looking easier than ever.</p> <h4>A wider foundation: Sustainability through community (and Symfony)</h4> <p>Building Drupal on top of Symfony means even further specialization on the Drupal end. The new Drupal versions use Symfony for a host of standard web application tasks — things like form validation, data serialization, data storage and retrieval, content translation, templating, easy and human-readable configuration management with YAML — the list goes on. Building on top of Symfony lets Drupal developers build closer to the <i>consumer level</i>, i.e., build the media libraries, authoring experiences, layout managers, etc that really matter to users.</p> <p>Drupal’s reliance on Symfony — a robust and mature web application framework with more than 600,000 registered developers — also translates to greater sustainability through a larger active community. Besides adding Symfony’s 600K developers to Drupal’s roughly 1 million, the move to a modern architecture and more collaborative versioning system makes Drupal <i>more attractive to developers on the leading edge of their disciplines</i>. That fact alone stands to benefit the Drupal community through new, top-tier talent driving further innovation.</p> <h4>Regular new stuff: Incremental feature releases</h4> <p>With Drupal’s new versioning cycle comes another benefit: major new features in minor version core releases. As Drupal 8 churns through minor releases (the second place in the three place version number, e.g. “y” in x.y.z) it continues to add brand new features to core. Unlike previous major Drupal versions, these aren’t just bug fixes and security upgrades. Game changing modules like <a href="https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module">JSON API</a>, <a href="https://www.drupal.org/docs/8/core/modules/media">Media</a>, and <a href="https://www.drupal.org/docs/8/core/modules/layout-builder">Layout Builder</a> were all added to Drupal 8 in minor version releases — and more are in the works.</p> <p>What does this mean for end users? A greater commitment to standardized features that meet primary needs (like media and layout management, for example) and a bigger pool of developers making sure those features are updated, stable, and compatible.</p> <hr /> <p>I just finished one of my first Drupal 8/9 upgrades yesterday for a small searchable database of computer science tips &amp; tricks run by a college out of Claremont, California. Admittedly it was a pretty simple site, but the upgrade took me just two hours. Wow.</p> <p>Aten will be working on a variety of Drupal 8 to Drupal 9 upgrades in the coming months, but the landscape is almost unrecognizable compared to our Drupal 7 to Drupal 8 efforts. For us, for the rest of the Drupal world, and for our clients, that is real good news.</p> <!-- google doc id: 1-BjDI6YKjsXvdvwR8mJdvd_cnUB6Tx7uvR_np9AFOxo --></div> <a href="/about/joel-steidl" hreflang="en">Joel Steidl</a> Thu, 11 Mar 2021 18:41:57 +0000 Joel Steidl 10231 at https://atendesigngroup.com https://atendesigngroup.com/articles/drupal-7-drupal-8-9-and-beyond-your-last-major-upgrade#comments Site building with Drupal Layout Builder https://atendesigngroup.com/webinar/site-building-drupal-layout-builder <span>Site building with Drupal Layout Builder</span> <article data-history-node-id="10178" role="article" about="/about/james-nettik" class="js--block-link profile profile--compact" data-href="/about/james-nettik" > <div class="profile__image"> <figure> <picture> <source srcset="/sites/default/files/james-nettik.png 1x" media="(min-width: 1860px)" type="image/png"/> <source srcset="/sites/default/files/james-nettik.png 1x" media="(min-width: 1540px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_large/public/james-nettik.png?itok=6tIJjejL 1x" media="(min-width: 1265px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/james-nettik.png?itok=lySRzyZJ 1x" media="(min-width: 1024px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/james-nettik.png?itok=lySRzyZJ 1x" media="(min-width: 768px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/james-nettik.png?itok=lySRzyZJ 1x" media="(min-width: 600px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_small/public/james-nettik.png?itok=V7XKtki5 1x" media="(min-width: 500px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_small/public/james-nettik.png?itok=V7XKtki5 1x" media="(min-width: 0)" type="image/png"/> <img src="/sites/default/files/james-nettik.png" alt="" typeof="foaf:Image" /> </picture> </figure> </div> <div class="profile__content clearfix"> <h3 class="profile__title"> <a href="/about/james-nettik" class="profile__title-link">James Nettik</a> </h3> <div class="profile__job-title">Senior Developer Manager</div> </div> </article> <article data-history-node-id="10227" role="article" about="/about/jess-snyder" class="js--block-link profile profile--compact" data-href="/about/jess-snyder" > <div class="profile__image"> <figure> <picture> <source srcset="/sites/default/files/2021-03/jess-snyder.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-03/jess-snyder.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-03/jess-snyder.jpg?itok=TVUCE0II 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-03/jess-snyder.jpg?itok=akSjrcah 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-03/jess-snyder.jpg?itok=akSjrcah 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-03/jess-snyder.jpg?itok=akSjrcah 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-03/jess-snyder.jpg?itok=xpg3MQh0 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-03/jess-snyder.jpg?itok=xpg3MQh0 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-03/jess-snyder.jpg" alt="Jess Snyder" typeof="foaf:Image" /> </picture> </figure> </div> <div class="profile__content clearfix"> <h3 class="profile__title"> <a href="/about/jess-snyder" class="profile__title-link">Jess Snyder</a> </h3> <div class="profile__job-title">Senior Manager, Web Systems at WETA</div> </div> </article> <span><a title="View user profile." href="/user/justin-toupin" lang="" about="/user/justin-toupin" typeof="schema:Person" property="schema:name" datatype="">Justin Toupin</a></span> <span>Wed, 03/10/2021 - 15:57</span> <div class="field field--name-field-zoom-webinar-agenda field--type-string-long field--label-hidden field__item">Join Senior Front-end Developer James Nettik of Aten Design Group and Senior Manager of Web Systems Jess Snyder of WETA for an in-depth look at Layout Builder in Drupal core.<br /> <br /> In this one-hour session we&#039;ll cover:<br /> <br /> - When Layout Builder is the right fit for your project.<br /> - How to use Layout Builder to create default layouts for content types.<br /> - When, why, and how to create page-specific overrides for your content.<br /> <br /> Who is this for:<br /> <br /> - Site builders working with Drupal.<br /> - Teams considering a move to Drupal 8 or 9.<br /> - Content authors looking for more control over their website content, without having to write code.</div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Join Senior Front-end Developer James Nettik of Aten Design Group and Senior Manager of Web Systems Jess Snyder of <a href="https://weta.org">WETA</a> for an in-depth look at Layout Builder in Drupal core.</p> <h2>In this one-hour session we'll cover:</h2> <ul> <li>When Layout Builder is the right fit for your project.</li> <li>How to use Layout Builder to create default layouts for content types.</li> <li>When, why, and how to create page-specific overrides for your content.</li> </ul> <h2>Who is this for:</h2> <ul> <li>Site builders working with Drupal.</li> <li>Teams considering a move to Drupal 8 or 9.</li> <li>Content authors looking for more control over their website content, without having to write code.</li> </ul></div> <div class="field field--name-field-date field--type-daterange field--label-above"> <div class="field__label">Date</div> <div class="field__item"><time datetime="2021-03-24T18:00:00Z">Wed, 03/24/2021 - 12:00</time> - <time datetime="2021-03-24T19:00:00Z">Wed, 03/24/2021 - 13:00</time> </div> </div> <div class="field field--name-field-zoom-webinar-join-url field--type-string field--label-hidden field__item">https://atendesigngroup.zoom.us/j/97081500402</div> <a href="/articles/authoring-experience" class="tag" >Authoring Experience</a> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> Wed, 10 Mar 2021 22:57:57 +0000 Justin Toupin 10228 at https://atendesigngroup.com Expressive Authoring with Layout Paragraphs https://atendesigngroup.com/articles/expressive-authoring-layout-paragraphs <span>Expressive Authoring with Layout Paragraphs</span> <figure> <picture> <source srcset="/sites/default/files/2020-11/layout-paragraphs.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2020-11/layout-paragraphs.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2020-11/layout-paragraphs.jpg?itok=hjIdtBJs 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2020-11/layout-paragraphs.jpg?itok=Oi4UHv9j 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2020-11/layout-paragraphs.jpg?itok=Oi4UHv9j 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2020-11/layout-paragraphs.jpg?itok=Oi4UHv9j 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2020-11/layout-paragraphs.jpg?itok=8pCgbeNb 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2020-11/layout-paragraphs.jpg?itok=8pCgbeNb 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2020-11/layout-paragraphs.jpg" alt="Layout Paragraphs Module" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/justin-toupin" lang="" about="/user/justin-toupin" typeof="schema:Person" property="schema:name" datatype="">Justin Toupin</a></span> <span>Wed, 11/18/2020 - 11:30</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <a href="/blog/category/user-experience" class="tag" >User Experience</a> <a href="/articles/authoring-experience" class="tag" >Authoring Experience</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><h2>The Authoring Experience Problem</h2> <p>Authoring experience is a huge concern for web teams everywhere. As it should be. The ability for digital publishers to keep up with the rapidly accelerating demands of their users, and the pace of their competitors, largely depends on providing easy-to-use, expressive tools for their teams to create compelling content.</p> <p>In practice, this means that publishers need the ability to quickly produce rich digital content from a range of possible components: text, images, videos, social media assets, and so on. Authors should be able to mix various elements quickly and effortlessly, with simple controls for managing content flow and layout. Under the hood, content should be structured and predictable. We’ve written about this before: <a href="https://atendesigngroup.com/articles/flexible-authoring-structured-content">organizations cannot sacrifice structure for flexibility</a>. They need both.</p> <p>And yet authoring experience has been a major shortcoming of Drupal, often articulated by marketing and editorial leaders as, “Drupal is too complicated for my organization.”</p> <p>Other platforms from across the entire spectrum of digital publishing systems – from Adobe Experience Manager to Wordpress, Squarespace to WiX – provide rich, intuitive tools for easily creating all kinds of digital content. What Drupal offers in terms of flexibility – powerful APIs, easily customizable structured content, and a huge range of content management features – has come at the expense of the authoring experience.</p> <h2>Layout Paragraphs</h2> <p>A while ago I wrote about <a href="https://atendesigngroup.com/articles/drupal-8-paragraphs-layout-discovery">Entity Reference with Layout (or ERL)</a>, a Drupal 8 module that combines <a href="https://www.drupal.org/project/paragraphs">Paragraphs</a> with the <a href="https://www.drupal.org/docs/drupal-apis/layout-api">Layout API</a> for a more flexible, expressive authoring experience in Drupal. ERL had one concerning drawback: you had to use a special field type for referencing paragraphs. That means ERL wouldn’t “just work” with existing paragraph fields.</p> <p><a href="https://www.drupal.org/project/layout_paragraphs">Layout Paragraphs</a>, the successor to ERL, fixes that problem. The Layout Paragraphs module combines Drupal Paragraphs with the Layout API and works seamlessly with existing paragraph reference fields.</p> <p><div data-embed-button="media_browser" data-entity-embed-display="view_mode:media.embedded" data-entity-type="media" data-entity-uuid="d8ad4929-d00f-4892-8ca0-da70a5fef626" data-langcode="en" class="embedded-entity"><figure> <div class="field field--name-field-media-video-file field--type-file field--label-visually_hidden"> <div class="field__label visually-hidden">Video file</div> <div class="field__item"><video controls="controls" width="640" height="480"> <source src="/sites/default/files/2020-11/Layout-Paragraphs.mp4" type="video/mp4"></source> </video> </div> </div> </figure> </div> </p> <p>The Layout Paragraphs module makes it dead simple for authors to build rich pages from a library of available components. It offers drag-and-drop controls for managing content flow and layout, and works seamlessly with existing paragraph reference fields.</p> <p><b>Layout Paragraphs features include: </b></p> <ul> <li>Easily configure which content components, or paragraph types, are available to your authors wherever Layout Paragraphs is used.</li> <li>A highly visual, drag-and-drop interface makes it simple to manage content flow and layout.</li> <li>Site admins can easily configure Layout Paragraphs to support nested layouts up to 10 levels deep.</li> <li>A “disabled bin” makes it easy to temporarily remove content elements without deleting them.</li> <li>Layout Paragraphs works seamlessly with existing paragraph reference fields.</li> <li>Authors can easily switch between different layouts without losing nested content.</li> <li>Layout Paragraphs is fully customizable and works with Layout Plugins, Paragraph Behaviors, and other Drupal APIs.</li> </ul> <h2>What About Layout Builder?</h2> <p>While there are strong similarities between Layout Paragraphs and Layout Builder, Layout Paragraphs solves a fundamentally different problem than Layout Builder.</p> <p><i>Layout Builder</i>, in Drupal core, is a powerful system for managing layouts across an entire website. With Layout Builder, site administrators and content managers can place content from virtually anywhere (including custom blocks) within specific regions of a layout. Layout Builder is extremely powerful, but doesn’t directly solve the problem I mentioned above: “Drupal is too complicated for my organization.”</p> <p><i>Layout Paragraphs</i> adds a new interface for effortlessly managing content using Drupal Paragraphs. It is simple, fast, and expressive. Layout Paragraphs is a field widget, and works the same way as other Drupal fields. The Layout Paragraphs module makes it simple for publishers to create and manage rich content from a library of available components: text, images, videos, etc. Layout Paragraphs is completely focused on the authoring experience.</p> <h2>Try It Out</h2> <p>If you want to see Layout Paragraphs in action, simply download the module and give it a try. Setup is fast and easy. From the Layout Paragraphs module page:</p> <ol> <li>Make sure the <a href="https://www.drupal.org/project/paragraphs">Paragraphs module </a>is installed.</li> <li>Download/Require (composer require drupal/layout_paragraphs) and install Layout Paragraphs.</li> <li>Create a new paragraph type (admin &gt; structure &gt; paragraph types) to use for layout sections. Your new paragraph type can have whatever fields you wish, although no fields are required for the module to work.</li> <li>Enable the “Layout Paragraphs” paragraph behavior for your layout section paragraph type, and select one or more layouts you wish to make available.</li> <li>Make sure your new layout section paragraph type is selected under “Reference Type” on the content type’s reference field edit screen by clicking “edit” for the respective field on the “Manage fields” tab.</li> <li>Choose “Layout Paragraphs” as the field widget type for the desired paragraph reference field under “Manage form display”.</li> <li>Choose “Layout Paragraphs” as the field formatter for the desired paragraph reference field under “Manage display”.</li> <li>That’s it. Start creating (or editing) content to see the module in action.</li> </ol> <p>If you’re using Layout Paragraphs in your projects, we’d love to hear about it. Drop a note in the comments section below, or get involved in the <a href="https://www.drupal.org/project/issues/layout_paragraphs">issue queue</a>.</p> <!-- google doc id: 1QOz7bbiW9Jd7QCC8kLuJ3YzRiELKMRO6uFJ1Ts3s68M --></div> <div class="c-article-list"> <h3 class="c-article-list__title">Read This Next</h3> <div class="item-list"> <ul> <li><a href="/articles/drupal-8-paragraphs-layout-discovery" hreflang="und">Drupal 8 Paragraphs + Layout Discovery</a></li> <li><a href="/articles/flexible-authoring-structured-content" hreflang="und">Flexible Authoring with Structured Content </a></li> </ul> </div> </div> <a href="/about/justin-toupin" hreflang="en">Justin Toupin</a> Wed, 18 Nov 2020 18:30:00 +0000 Justin Toupin 10197 at https://atendesigngroup.com https://atendesigngroup.com/articles/expressive-authoring-layout-paragraphs#comments Absolute menu links in Drupal 8 https://atendesigngroup.com/articles/absolute-menu-links-drupal-8 <span>Absolute menu links in Drupal 8</span> <span><a title="View user profile." href="/user/joel-steidl" lang="" about="/user/joel-steidl" typeof="schema:Person" property="schema:name" datatype="">Joel Steidl</a></span> <span>Thu, 11/12/2020 - 12:15</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <a href="/blog/category/development" class="tag" >Code</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p>Creating absolute menu links in Drupal 8 is pretty simple, but it took a little thought to land on the best solution for my use case. I ran into the requirement while working with a client to deploy their site navigation to a REACT app sitting on a different domain. The menu links we pulled in from their primary website needed to point back to their domain correctly, which of course meant they needed to be absolute.</p> <p>A quick search turned up a couple of solutions, but none that quite suited my requirements. Here’s what I came up with — it’s fairly concise, leaves the menu link attributes intact, and doesn’t involve the theme layer.</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #009933; font-style: italic;">/** * Implements hook_preprocess_menu(). * */</span> <span style="color: #000000; font-weight: bold;">function</span> MYTHEME_preprocess_menu<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$variables</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #666666; font-style: italic;">// Limit to specific menus for performance.</span> <span style="color: #000088;">$menus</span> <span style="color: #339933;">=</span> <span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'main'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'footer'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><a href="http://www.php.net/isset"><span style="color: #990000;">isset</span></a><span style="color: #009900;">&#40;</span><span style="color: #000088;">$variables</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'menu_name'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">&amp;&amp;</span> <a href="http://www.php.net/in_array"><span style="color: #990000;">in_array</span></a><span style="color: #009900;">&#40;</span><span style="color: #000088;">$variables</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'menu_name'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$menus</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> MYTHEME_set_menu_items_to_absolute<span style="color: #009900;">&#40;</span><span style="color: #000088;">$variables</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'items'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span> <span style="color: #009900;">&#125;</span> &nbsp; <span style="color: #009933; font-style: italic;">/** * Recursively make menu items link with absolute. * */</span> <span style="color: #000000; font-weight: bold;">function</span> MYTHEME_set_menu_items_to_absolute<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$items</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$items</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$item</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #000088;">$url</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$item</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'url'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #000088;">$url</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">isExternal</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #000088;">$item</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'url'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$url</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setOption</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'absolute'</span><span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span> <span style="color: #666666; font-style: italic;">// Recursively loop of sub-menu items.</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><a href="http://www.php.net/isset"><span style="color: #990000;">isset</span></a><span style="color: #009900;">&#40;</span><span style="color: #000088;">$item</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'below'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> MYTHEME_set_menu_items_to_absolute<span style="color: #009900;">&#40;</span><span style="color: #000088;">$item</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'below'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span> <span style="color: #009900;">&#125;</span> <span style="color: #009900;">&#125;</span></pre></div></pre> <p>Generating absolute menu links this way means&nbsp;I can pull main navigation onto various apps without any headache and without sacrificing any Drupal theme layer functionality.</p> <p><a href="https://gist.github.com/joelsteidl/26d9330a1c7994c59c5ebd2f7d5bb49f">You can download the gist here.</a> Make sure you specify which menus you'd like to act on, by machine name, in the first line of the&nbsp;<em>MYTHEME_preprocess_menu()</em> function. The code goes in <em>mytheme.theme</em>&nbsp;— don't forget to replace <em>MYTHEME</em> with your theme name throughout.</p> <p>Do you have a simpler way? Feel free to share in the comments.</p> <!-- google doc id: 1l7kKEKkKpRjXfwipoX7Z06o9uLVJ6LRm-SmxuENInaE --></div> <a href="/about/joel-steidl" hreflang="en">Joel Steidl</a> Thu, 12 Nov 2020 19:15:00 +0000 Joel Steidl 10169 at https://atendesigngroup.com https://atendesigngroup.com/articles/absolute-menu-links-drupal-8#comments Add to Calendar buttons with AddEvent for Drupal 8 https://atendesigngroup.com/articles/add-calendar-buttons-addevent-drupal-8 <span>Add to Calendar buttons with AddEvent for Drupal 8</span> <span><a title="View user profile." href="/user/travis-tomka" lang="" about="/user/travis-tomka" typeof="schema:Person" property="schema:name" datatype="">Travis Tomka</a></span> <span>Tue, 10/20/2020 - 16:28</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><p>The <a href="https://www.drupal.org/project/addevent">AddEvent module for Drupal 8</a> lets you place easy-to-use <i>Add to Calendar</i> buttons on your website that integrate with Apple, Google, Yahoo, Outlook, and Office 365 calendar apps among others. The module also lets your users subscribe to entire calendars managed by AddEvent, adding all of your scheduled events to their calendars and keeping their calendars updated if your event details change.</p> <p>The module is powered by <a href="https://www.addevent.com/">AddEvent</a> and you’ll need an AddEvent account for it to work properly. Free accounts include one calendar, restricted event-adds, and limited functionality; paid accounts include multiple calendars, higher subscriber limits, and a wide variety of other features. You’ll be able to build <i>Add to Calendar </i>proof-of-concept functionality with the AddEvent free version in just 15-20 minutes — but a professional tier account is necessary for any commercial level use.</p> <h3>First things first: Create an AddEvent account</h3> <p>Head over to AddEvent.com and register for an account. AddEvent is a paid service that offers a variety of features and high volume usage for licensed customers — but they also provide a free <i>Hobby</i> account that will get you started. After creating and verifying your account with the free <i>Hobby</i> plan, navigate to the Account screen by clicking on the user menu in the upper right corner of the screen, then clicking Account. From there, click <i>Domains</i> in the left-hand navigation, then follow instructions to add your domain.</p> <figure role="group" class="align-center"> <div alt="AddEvent Module: domain whitelisting" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="fa936765-8351-4bfe-9562-e08d6cc6258a" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_licensed_domains.png" alt="AddEvent Module: domain whitelisting" title="AddEvent Module: domain whitelist" typeof="foaf:Image" /> </div> <figcaption>The domain where you will be using an Add to Calendar button needs to be whitelisted with AddEvent.</figcaption> </figure> <p>The <i>Hobby </i>plan only allows the use of one domain — but you’re able (at the time of writing this, anyway) to add a local development environment so that you can test without deploying to a publicly accessible website. When you’re ready to make the switch, you can simply remove your development domain and add in your live domain.</p> <h3>Install the Drupal 8 AddEvent module</h3> <p>Once you’ve registered for the service you’ll need to install the AddEvent module in your Drupal 8 website. You can <a href="https://www.drupal.org/docs/extending-drupal/installing-modules">install a Drupal 8 module</a> in a variety of ways, but the easiest is probably using your terminal, starting from your project’s root directory. First require the module using composer:</p> <p><span class="geshifilter"><code class="php geshifilter-php">composer <span style="color: #b1b100;">require</span> drupal<span style="color: #339933;">/</span>addevent</code></span></p> <p>Then enable the module using drush:</p> <p><span class="geshifilter"><code class="php geshifilter-php">drush en addevent</code></span></p> <p>Now you’re all set to start using the AddEvent module. You can immediately create single-use <i>Add to Calendar</i> buttons using custom blocks, or combine custom blocks with a custom <i>Event</i> node type and the <a href="https://www.drupal.org/project/token">Token</a> module to automatically produce <i>Add to Calendar</i> buttons for all of your events.</p> <h3>Add to Calendar: Static event buttons</h3> <p>Creating a static <i>Add to Calendar</i> button for a single event is super simple. Before we configure a block with our <i>Add to Calendar</i> link, though, we’ll need to fetch our <i>Client ID</i> from AddEvent. To do that, simply navigate to the <i>Account</i> page at <a href="https://www.addevent.com/">AddEvent.com</a> using the menu in the upper right corner of the screen. You should see a <i>Client ID</i> and <i>API Token</i> — just keep this window open or paste the <i>Client ID</i> in a safe place.</p> <figure role="group" class="align-center"> <div alt="AddEvent Module: Client ID" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="be6d5d54-b3e9-4986-8b63-cd93c8588430" data-langcode="en" title="AddEvent Module: Client ID" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_client_id_0.png" alt="AddEvent Module: Client ID" title="AddEvent Module: Client ID" typeof="foaf:Image" /> </div> <figcaption>You'll need the Client ID to render your Add to Calendar buttons without the AddEvent branding.</figcaption> </figure> <p>Now on your Drupal site navigate to <i>Structure</i> &gt; <i>Block layout</i> (/admin/structure/block) and click on <i>Place block</i> in the region you would like to place your <i>Add to Calendar</i> button. In my case, I’ll demonstrate adding the button to the <i>Content</i> region of a basic page that lives at the URL <em>/my-basic-page</em>.</p> <p>From the <i>Place a block</i> dialogue that follows, click <i>Place block</i> next to the <i>Add to Calendar</i> item. Fill out the following fields as indicated to get your first example working:</p> <ul> <li><b>Title:</b> Add to Calendar</li> <li><b>Display title:</b> <i>not checked </i></li> <li><b>Button text:</b> Add to Calendar</li> <li><b>Title:</b> My example event!</li> <li><b>Description:</b> My example event description!</li> <li><b>Start:</b> 10/31/2020 8:00 AM (the format is important, the date isn’t)</li> <li><b>End:</b> 10/31/2020 9:15 AM</li> <li><b>Client:</b> [Client ID from above]</li> </ul> <p>Under the <i>Customization</i> portion of the form, paste your <i>Client ID</i> in the <i>License</i> field. This will remove the AddEvent branding from your button dialogue.</p> <p>Finally, in the <i>Visibility</i> section of your block setup, choose <i>Pages</i> and write in the URL of the page you’d like the block on — in my case I’ll use /my-basic-page. Click <i>Save block</i> and navigate to the page you configured your block to appear on.</p> <figure role="group" class="align-center"> <div alt="AddEvent module, an Add to Calendar button." data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="fcfc3ea2-8cc2-44d0-8c26-17ccea486baa" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_placed_block.png" alt="AddEvent module, an Add to Calendar button." title="AddEvent Module: Placed block" typeof="foaf:Image" /> </div> <figcaption>A basic Add to Calendar button will present options for adding to Google, Yahoo, Apple, Office 365, Outlook calendars and more.</figcaption> </figure> <p>That’s it! Click the button and choose a calendar type to add the event to your calendar — it’s that easy. Next we’ll look at dynamically adding buttons to a custom <i>Event</i> node page.</p> <h3>Add to Calendar: Dynamic event buttons</h3> <p>You can create dynamic event buttons by using the Token module with a custom <i>Event</i> content type. Any node type with a properly configured date field will work. Make sure the <a href="https://www.drupal.org/project/token">Token</a> module is installed before proceeding — use whatever <a href="https://www.drupal.org/docs/extending-drupal/installing-modules">Drupal module installation method</a> works best for you. For this example, we’ll create a super simple <i>Event</i> node type. In my example below, I’ve added two custom fields of the <i>Date</i> type: <i>field_event_date_start</i> and <i>field_event_date_end</i>.</p> <figure role="group" class="align-center"> <div alt="AddEvent module: Event content type" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="234afeaa-1ccd-4f96-9785-3e091987323c" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_event_ct.png" alt="AddEvent module: Event content type" title="AddEvent module: Event content type" typeof="foaf:Image" /> </div> <figcaption>A custom <em>Event</em> node type with date fields for start date and end date, for use with dynamically created Add to Calendar buttons.</figcaption> </figure> <p>Once your <i>Event</i> content type is set up, you just need to add a new Date format that will work well with the AddEvent module. To do that, navigate to <i>Configuration</i> &gt; <i>Date and time formats</i> (/admin/config/regional/date-time) and click the <i>Add format</i> button. You can see below that I’m calling my format <i>AddEvent</i> and using the format string <i>m/d/y g:i A</i> which will translate to something like 10/30/20 4:45 PM — exactly what we need for our <i>Add to Calendar</i> button.</p> <figure role="group" class="align-center"> <div alt="AddEvent module: date format" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="280c81f9-371b-45ea-bb30-8738811ae120" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_date_format.png" alt="AddEvent module: date format" title="AddEvent module: Date format" typeof="foaf:Image" /> </div> <figcaption>Define a custom AddEvent compatible date format to get your <em>Event</em> content type working with Add to Calendar buttons.</figcaption> </figure> <p>Now we’ll place a new <i>Add to Calendar</i> block just like in the previous section, only we’ll use tokens for the Event title, description, and the start and end dates. When you open the <i>Configure block</i> interface for your new <i>Add to Calendar</i> block, click the <i>Browse available tokens</i> link in the description area of the <i>Event Information</i> section. The tokens you’re looking for will be in the <i>Node</i> group. Notice that the token options for <i>field_event_date_start</i> and <i>field_event_date_end</i> indicate our custom AddEvent format — tokens will be available for all of your custom date formats. Check out my example below.</p> <figure role="group" class="align-center"> <div alt="AddEvent module: tokens" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="cb205452-018c-48c0-97dd-392b7f162840" data-langcode="en" title="AddEvent module: tokens" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_dynamic.png" alt="AddEvent module: tokens" title="AddEvent module: tokens" typeof="foaf:Image" /> </div> <figcaption>The Token module lets block configuration be dynamic, allowing your Add to Calendar buttons to pull data from <em>Event</em> pages.</figcaption> </figure> <p>Everything else will be the same as it was in the first example in the section above, with the exception of the <i>Visibility</i> settings. For this block, we’ll restrict visibility by <i>Content Type</i>, ensuring that the block only appears on our custom <i>Event</i> node type pages.</p> <figure role="group" class="align-center"> <div alt="AddEvent module: Block visibility" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="ff9eb16f-df35-4025-8c7a-67ec988d9a23" data-langcode="en" title="AddEvent module: Block visibility" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_dynamic_visibility.png" alt="AddEvent module: Block visibility" title="AddEvent module: Block visibility" typeof="foaf:Image" /> </div> <figcaption>Set block visibility to the <em>Event</em> content type to automatically create an Add to Calendar button on <em>Event</em> pages.</figcaption> </figure> <p>Once you’ve clicked <i>Save block</i> you’re all set. Now your <i>Add to Calendar</i> button will appear on every <i>Event</i> page in the region you specified, and it will inherit all of the event details from the specific event page.</p> <h3>Subscribe to an AddEvent calendar</h3> <p>Paid AddEvent accounts can manage multiple calendars of events on the AddEvent dashboard, and they have access to robust features like RSVP, custom fielded calendar events, subscriber reports, analytics, and more. While the <i>Hobby</i> account can demonstrate how <i>Subscribe to Calendar</i> works, the feature isn’t much use with the free-version restrictions in place.</p> <p>Setting up a <i>Subscribe to Calendar</i> button is super simple. Before setting up the block on your Drupal site, you’ll need the <i>Unique Key</i> for your AddEvent calendar. From your Dashboard on AddEvent.com, click the menu options icon next to your calendar of choice (<i>Hobby</i> accounts only have one calendar) and click <i>Calendar Page</i>. Now copy the <i>Unique Key</i> for use on the block configuration below</p> <figure role="group" class="align-center"> <div alt="AddEvent: Calendar Unique Key" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="e69cc6db-e33d-410f-8939-8834ce5f7f76" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_unique_key_0.png" alt="AddEvent: Calendar Unique Key" title="AddEvent: Calendar Unique Key" typeof="foaf:Image" /> </div> <figcaption>The Unique Key of your calendar used with the <em>Subscribe to Calendar</em> block type subscribes your users to entire AddEvent calendars.</figcaption> </figure> <p>On your Drupal site, navigate to <i>Structure</i> &gt; <i>Block layout</i> (/admin/structure/block) and click <i>Place block</i> in the region of your choice. Choose the <i>Subscribe to Calendar</i> block type, then paste your <i>Unique Key</i> from above in the <i>Data-ID</i> field. You may want to place your <i>Client ID</i> in the <i>License</i> field like in previous configurations, to remove the AddEvent branding from the interface.</p> <p>You can set up the Visibility settings however you wish, for my example I’ll stick with the /my-basic-page URL like before.</p> <figure role="group" class="align-center"> <div alt="AddEvent module: Subscribe to calendar" data-embed-button="media_browser" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="d70401ee-404b-4f1a-a288-b86ea802c356" data-langcode="en" class="embedded-entity"> <img src="/sites/default/files/2020-10/addevent_aten_subscribe_cal.png" alt="AddEvent module: Subscribe to calendar" title="AddEvent module: Subscribe to calendar" typeof="foaf:Image" /> </div> <figcaption>Subscribe to Calendar buttons add all of the events in an AddEvent calendar to your users calendar, and updates them regularly.</figcaption> </figure> <p>You’re all done! Now your users can subscribe to the calendar(s) you maintain on your AddEvent account. As you add and edit events on your AddEvent calendars, your subscribers’ calendars will be automatically updated. Very cool!</p> <p><strong>NOTE:</strong> Different calendar services synchronize at different speeds and some are very slow. Google Calendars, for example, can take up to 24 hours to synchronize changes from calendar subscriptions.</p> <p>If you have suggestions, comments, or issues related to the AddEvent module, please let me know on the <a href="https://www.drupal.org/project/addevent">AddEvent module page</a>! And feel free to leave questions or general comments below.</p> <!-- google doc id: 1Pp0XyjrTAkFB2WnEg3yQaj68cUqizMDkDfmvdKIRq18 --></div> <a href="/about/travis-tomka" hreflang="en">Travis Tomka</a> Tue, 20 Oct 2020 22:28:20 +0000 Travis Tomka 10159 at https://atendesigngroup.com https://atendesigngroup.com/articles/add-calendar-buttons-addevent-drupal-8#comments