Drupal https://atendesigngroup.com/ en Mercury Editor: Effortless, drag-and-drop publishing for Drupal https://atendesigngroup.com/webinar/mercury-editor-effortless-drag-and-drop-publishing-drupal <span>Mercury Editor: Effortless, drag-and-drop publishing for 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>Mon, 09/20/2021 - 12:52</span> <div class="field field--name-field-zoom-webinar-agenda field--type-string-long field--label-hidden field__item">Mercury Editor: Effortless, drag-and-drop publishing for Drupal</div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><p style="line-height:1.5; margin-bottom:13px"><span style="font-size:12pt; font-variant-ligatures:normal; font-variant-east-asian:normal; font-variant-position:normal; white-space:pre-wrap"><span style="font-family:Roboto, sans-serif"><span style="color:#363136"><span style="font-weight:400"><span style="font-style:normal"><span style="text-decoration:none">Introducing Mercury Editor: Effortless drag-and-drop publishing for Drupal, brought to you by Aten Design Group.</span></span></span></span></span></span></p> <p style="line-height:1.5; margin-bottom:13px"><span style="font-size:12pt; font-variant-ligatures:normal; font-variant-east-asian:normal; font-variant-position:normal; white-space:pre-wrap"><span style="font-family:Roboto, sans-serif"><span style="color:#363136"><span style="font-weight:400"><span style="font-style:normal"><span style="text-decoration:none">Mercury Editor brings flexible, highly visual, easy-to-use content editing to Drupal. It works with existing Drupal websites, without any licensing fees or restrictions. </span></span></span></span></span></span></p> <p style="line-height:1.5; margin-bottom:13px"><span style="font-size:12pt; font-variant-ligatures:normal; font-variant-east-asian:normal; font-variant-position:normal; white-space:pre-wrap"><span style="font-family:Roboto, sans-serif"><span style="color:#363136"><span style="font-weight:400"><span style="font-style:normal"><span style="text-decoration:none">In this webinar, we’ll walk through the components of a new, easy-to-use, drag-and-drop authoring experience based on Layout Paragraphs, that empowers teams to publish rich, beautiful content in Drupal, quickly. This session is for everyone in the Drupal community, from developers to designers to UX specialists. It is </span></span></span></span></span></span><span style="font-size:12pt; font-variant-ligatures:normal; font-variant-east-asian:normal; font-variant-position:normal; white-space:pre-wrap"><span style="font-family:Roboto, sans-serif"><span style="color:#363136"><span style="font-weight:400"><span style="font-style:italic"><span style="text-decoration:none">especially</span></span></span></span></span></span><span style="font-size:12pt; font-variant-ligatures:normal; font-variant-east-asian:normal; font-variant-position:normal; white-space:pre-wrap"><span style="font-family:Roboto, sans-serif"><span style="color:#363136"><span style="font-weight:400"><span style="font-style:normal"><span style="text-decoration:none"> for marketing, communications and content management teams to build, edit, and publish beautiful content in their Drupal websites with ease.</span></span></span></span></span></span></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-10-13T15:00:00Z">Wed, 10/13/2021 - 09:00</time> - <time datetime="2021-10-13T16:00:00Z">Wed, 10/13/2021 - 10: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/89045639945</div> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <a href="/articles/authoring-experience" class="tag" >Authoring Experience</a> Mon, 20 Sep 2021 18:52:01 +0000 jenna 10321 at https://atendesigngroup.com An innovative Drupal 7 / Drupal 8 commerce solution https://atendesigngroup.com/articles/innovative-drupal-7-drupal-8-commerce-solution <span>An innovative Drupal 7 / Drupal 8 commerce solution</span> <figure> <picture> <source srcset="/sites/default/files/2021-09/DRUPAL_COMMERCE_02.jpg 1x" media="(min-width: 1860px)" type="image/jpeg"/> <source srcset="/sites/default/files/2021-09/DRUPAL_COMMERCE_02.jpg 1x" media="(min-width: 1540px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-09/DRUPAL_COMMERCE_02.jpg?itok=Y7LZ3ywU 1x" media="(min-width: 1265px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-09/DRUPAL_COMMERCE_02.jpg?itok=4UhF_MEk 1x" media="(min-width: 1024px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-09/DRUPAL_COMMERCE_02.jpg?itok=4UhF_MEk 1x" media="(min-width: 768px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-09/DRUPAL_COMMERCE_02.jpg?itok=4UhF_MEk 1x" media="(min-width: 600px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-09/DRUPAL_COMMERCE_02.jpg?itok=OAeXX5bX 1x" media="(min-width: 500px)" type="image/jpeg"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-09/DRUPAL_COMMERCE_02.jpg?itok=OAeXX5bX 1x" media="(min-width: 0)" type="image/jpeg"/> <img src="/sites/default/files/2021-09/DRUPAL_COMMERCE_02.jpg" alt="Drupal commerce article illustration" typeof="foaf:Image" /> </picture> </figure> <span><a title="View user profile." href="/user/1558" lang="" about="/user/1558" typeof="schema:Person" property="schema:name" datatype="">jenna</a></span> <span>Wed, 09/08/2021 - 10:52</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <a href="/blog/category/drupal-8" class="tag" >Drupal 8</a> <div class="field field--name-body field--type-text-with-summary field--label-hidden t--body field__item"><h2>A Drupal e-commerce project</h2> <p><a href="https://www.drupal.org/project/commerce">Drupal Commerce</a> has been around for more than a decade. More than fifty thousand sites report using Commerce, and its maintainers have kept the suite of modules modern, flexible, and robust over the years for both <a href="https://www.drupal.org/drupal-7.0">Drupal 7</a> <i>and</i><a href="https://www.drupal.org/8"><i> </i></a><a href="https://www.drupal.org/8">Drupal 8/9</a> with the latest code for the Drupal 7 version released as recently as earlier this year.</p> <p>The <a href="https://www.nceo.org/">National Center for Employee Ownership</a> (NCEO) came to us in 2018 with an existing investment in a Drupal 7 Commerce site that had thousands of hours poured into it in terms of configuration, custom development, and order management. At the time their staff were manually entering email and telephone orders into the Drupal Commerce backend, and were hoping to upgrade to a full-featured, payment gateway enabled online store. They also wanted to<i> </i>develop a brand new public facing NCEO.org that better captured their organization and connected users with their content.</p> <p>At that time building a brand new public-facing website <i>and </i>upgrading an existing complex online store presented an interesting problem. Do we continue building on top of Drupal 7 knowing it will reach end-of-life in a couple of years? Or do we bite the bullet and invest in a new Drupal 8 solution with its promise of a future-proof approach to website development?</p> <p>Spoiler alert — the answer was both.</p> <h2>Drupal 7 <i>and</i> Drupal 8: The best of both worlds</h2> <p>NCEO was using a custom Drupal 7 implementation of Commerce and <a href="https://www.drupal.org/project/redhen">RedHen CRM</a> to manage nearly a thousand products, tens of thousands of customers, hundreds of thousands of order records, and complex coupon and membership pricing logic. Their existing platform represented an extensive investment — and their staff were already trained and proficient on the existing suite of tools. While the tides were definitely turning towards all things Drupal 8 in 2018, completely rebuilding the platform, migrating records, and training staff on a new system was a tough sell. <i>And it was the wrong solution. </i></p> <p>The right solution was to protect the existing investment, and building a lightweight Drupal 7 / Drupal 8 integration could do just that. Users needed to be able to <i>browse</i> NCEO’s content and products on a new Drupal 8 website, and then be seamlessly handed-off to a Drupal 7 Commerce backend to make payments or review order details. Luckily, Drupal’s architecture lends itself to just this sort of integration and synchronization across platforms.</p> <h2>A touch of tech</h2> <p>After extending NCEO’s Drupal 7 Commerce implementation to include an intuitive, turnkey customer checkout experience, all that was left to do was wire that backend to the brand new Drupal 8 NCEO.org that would roll out the following year.</p> <p>The solution had to synchronize both <i>users</i> and <i>products</i>. The NCEO team would continue managing their products on the Drupal 7 platform, but those product updates would need to automatically post to the Drupal 8 site where users would browse them. Additionally, users would need to see their custom membership pricing on the Drupal 8 site, and maintain their session and membership data as they were seamlessly directed to the Drupal 7 platform for checkout.</p> <h2>Single sign-on &amp; user data synchronization</h2> <p>We used the <a href="https://www.drupal.org/project/cas">CAS module</a> to create the “behind the scenes” single sign-on between the two sites, and to help synchronize the user role data that translates to special membership pricing and offers. When a user completes a membership purchase (on the Dupal 7 portion of the experience) their new user role is synchronized <i>back</i> to the Drupal 8 website — ensuring that they <i>see</i> the appropriate discounts and membership offers while browsing products, and later <i>receive those discounts</i> at checkout.</p> <h2>Product synchronization via the hook system</h2> <p>The NCEO team continues to manage products and customer records on the Drupal 7 portion of the platform. On the Drupal 7 side we built a simple webhook system that posts product update data using Drupal’s <a href="https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_http_request/7.x">drupal_http_request($url, $options)</a> everytime a product is created, updated, or deleted. We capture those actions with the insert, update, and delete <a href="https://api.drupal.org/api/drupal/modules%21node%21node.api.php/group/node_api_hooks/7.x">node hooks available in the Drupal 7 API</a>. In essence, every time someone makes a change to a product managed on the Drupal 7 platform, those changes are securely transmitted to the public facing Drupal 8 site.</p> <p>When the data posts to the public-facing NCEO.org Drupal 8 site the products are updated almost immediately, ensuring that users are consistently presented with the absolute latest product information. Product updates on the Drupal 8 side are accomplished by defining a controller to accept data at a specific endpoint or URL — I wrote a pretty detailed post about <a href="https://atendesigngroup.com/articles/capturing-webhooks-drupal-8">capturing webhooks in Drupal 8</a> sometime ago, complete with code samples.</p> <p>The integration is super lightweight and the end product is a seamless checkout experience. The solution lets the existing platform <i>continue working as designed </i>while rebuilding the primary public facing website with the more modern, evergreen Drupal 8.</p> <h2>Value before technology</h2> <p>As a developer there’s often an urge to design and build systems using the latest and greatest iterations of our favorite technologies. As the Director of Engineering at Aten, it’s important for me to consider <i>how to achieve our clients’ goals</i> in a way that’s cost effective, secure, and sustainable. Often that requires a mix of bleeding edge technology <i>and</i> creative out-of-the-box solutions that keep the people using the software at the center of the process.</p> <p>In this case we were not only able to protect an existing investment <i>while</i> delivering a brand-new Drupal 8 website, but also built out a flexible architecture that will be super easy to upgrade in the future. In place of a Drupal 7 Commerce and RedHen CRM backend, NCEO could easily drop in another customer records solution to plug into their Drupal 8 site and store — all with hardly any changes to the Drupal 8 side. Even now in 2021 the solution works great — and as Drupal users everywhere <a href="https://atendesigngroup.com/articles/upgrade-paths-your-drupal-7-website">decide what to do with their Drupal 7 assets</a>, NCEO is in a great place to consider next steps for the commerce administration aspect of their platform without having to think about a complete website rebuild.</p> <p>Interested in learning more about what innovative digital solutions can do for your organization? <a href="https://atendesigngroup.com/contact">We’d love to hear from you.</a></p> <!-- google doc id: 1oy88P87lB9wrOnWrueVseAPt6exiMouyZJBkFfP8770 --></div> <a href="/about/joel-steidl" hreflang="en">Joel Steidl</a> Wed, 08 Sep 2021 16:52:01 +0000 jenna 10305 at https://atendesigngroup.com Accessing the Layers of Help in Drupal https://atendesigngroup.com/webinar/accessing-layers-help-drupal <span>Accessing the Layers of Help in Drupal</span> <article data-history-node-id="10286" role="article" about="/about/amber-matz" class="js--block-link profile profile--compact" data-href="/about/amber-matz" > <div class="profile__image"> <figure> <picture> <source srcset="/sites/default/files/2021-08/amber_bw_2019.png 1x" media="(min-width: 1860px)" type="image/png"/> <source srcset="/sites/default/files/2021-08/amber_bw_2019.png 1x" media="(min-width: 1540px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_large/public/2021-08/amber_bw_2019.png?itok=w6_ThczD 1x" media="(min-width: 1265px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-08/amber_bw_2019.png?itok=wSvxeybj 1x" media="(min-width: 1024px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-08/amber_bw_2019.png?itok=wSvxeybj 1x" media="(min-width: 768px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_medium/public/2021-08/amber_bw_2019.png?itok=wSvxeybj 1x" media="(min-width: 600px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-08/amber_bw_2019.png?itok=J5UIDJU1 1x" media="(min-width: 500px)" type="image/png"/> <source srcset="/sites/default/files/styles/responsive_small/public/2021-08/amber_bw_2019.png?itok=J5UIDJU1 1x" media="(min-width: 0)" type="image/png"/> <img src="/sites/default/files/2021-08/amber_bw_2019.png" alt="Photo of Amber Matz, Production Manager and Trainer at Osio Labs/Drupalize.me" typeof="foaf:Image" /> </picture> </figure> </div> <div class="profile__content clearfix"> <h3 class="profile__title"> <a href="/about/amber-matz" class="profile__title-link">Amber Matz</a> </h3> <div class="profile__job-title">Production Manager and Trainer at Osio Labs/Drupalize.me</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>Wed, 08/04/2021 - 09:51</span> <div class="field field--name-field-zoom-webinar-agenda field--type-string-long field--label-hidden field__item"> From field captions to docs to asking better questions, there are many layers of “getting help with Drupal” available to you as a Drupal site owner, editor, or developer. In this webinar, Drupalize.Me trainer and Drupal Help Topics co-maintainer Amber Matz will talk about the kinds of help available to you—free and paid—from the core software, community, training organizations, and the internet at-large. By the end of this webinar, you will gain a broader awareness of the kinds of help available to you and how to access them more effectively.</div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>From field captions to docs to asking better questions, there are many layers of “getting help with Drupal” available to you as a Drupal site owner, editor, or developer. In this webinar, Drupalize.Me trainer and Drupal Help Topics co-maintainer, Amber Matz will talk about the kinds of help available to you—free and paid—from the core software, community, training organizations, and the internet at-large.</p> <p>By the end of this webinar, you will gain a broader awareness of the kinds of help available to you and how to access them more effectively.</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-08-25T18:00:00Z">Wed, 08/25/2021 - 12:00</time> - <time datetime="2021-08-25T19:00:00Z">Wed, 08/25/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/89207335274</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/u5aNRMAk25Q?autoplay=1&amp;start=0&amp;rel=0"></iframe> </div> </div> </figure> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> Wed, 04 Aug 2021 15:51:39 +0000 jenna 10287 at https://atendesigngroup.com Programmatically copy field data in Drupal 8 https://atendesigngroup.com/articles/programmatically-copy-field-data-drupal-8 <span>Programmatically copy field data in Drupal 8</span> <span><a title="View user profile." href="/user/john-ferris" lang="" about="/user/john-ferris" typeof="schema:Person" property="schema:name" datatype="">John Ferris</a></span> <span>Tue, 06/22/2021 - 09:54</span> <a href="/blog/category/drupal-0" class="tag" >Drupal</a> <a href="/blog/category/drupal-8" class="tag" >Drupal 8</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>The code to programmatically copy field data in Drupal 8 is pretty simple, but I wasn’t able to find any great examples for performing the operation at scale when I ran into the need myself. Hopefully the code sample below helps somebody save a few minutes of digging for solutions.</p> <p>My use case was straightforward: After more than a year of continual development on a client site, new patterns emerged in how content editors utilized certain entity reference fields. It became apparent that similar relationships across different content types used separate fields with different machine names. This made it tricky to aggregate and filter content. It also led to overly complex implementations of any custom functionality based on these relationships. One thing was clear: we needed the fields to be consistent across content types.</p> <p>Basically, I needed entity references stored in various older fields, let’s say <i>field_old_one</i> and <i>field_old_two</i>, to be copied into a single destination field we’ll call <i>field_new</i>.</p> <p>Once I’d added our <i>field_new</i> field to all the appropriate content types via the field UI, I needed to migrate the references <i>out of</i> the old fields, <i>and into</i> the new fields. What’s the best way to do this when manual GUI changes would take too long? A <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension%21module.api.php/function/hook_post_update_NAME/8.7.x"><i>hook_post_update_NAME</i></a> implementation was my answer.</p> <p>The code to programmatically copy field data in Drupal 8 isn’t the hard part. Assuming your source and destination field types are compatible, it’s just two quick lines:</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">/* @var $node \Drupal\node\NodeInterface $node */</span> <span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #000088;">$dest_field</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #000088;">$source_field</span><span style="color: #339933;">;</span> <span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></pre> <p>It gets complicated when you need to do this on hundreds or thousands of entities. We don’t want to load, process, and save all those entities at once as that would likely bring our server down. We need to batch the operation. Implementations of <i>hook_post_update_NAME</i> act as implementations of <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Form%21form.api.php/function/callback_batch_operation/8.7.x"><i>callback_batch_operation</i></a> — allowing us to execute a custom callback _<i>my_module_copy_field_values</i> across multiple HTTP requests and avoid PHP timeouts. First, we define that callback.</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #009933; font-style: italic;">/** * Copies the value from one field to another empty field. * * @param array $sandbox * The batch operation sandbox. * @param string $bundle * The node bundle. * @param string $source_field * The source field name. * @param string $dest_field * The destination field. * @param int $nodes_per_batch * The amount of nodes to update at a given time. */</span> <span style="color: #000000; font-weight: bold;">function</span> _my_module_copy_field_values<span style="color: #009900;">&#40;</span><a href="http://www.php.net/array"><span style="color: #990000;">array</span></a> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$sandbox</span><span style="color: #339933;">,</span> <span style="color: #000088;">$bundle</span><span style="color: #339933;">,</span> <span style="color: #000088;">$source_field</span><span style="color: #339933;">,</span> <span style="color: #000088;">$dest_field</span><span style="color: #339933;">,</span> <span style="color: #000088;">$nodes_per_batch</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">20</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #000088;">$storage</span> <span style="color: #339933;">=</span> \Drupal<span style="color: #339933;">::</span><span style="color: #004000;">entityTypeManager</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getStorage</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'node'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// Initialize some variables during the first pass through.</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</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;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'total'</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> <span style="color: #000088;">$query</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$storage</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getQuery</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">-&gt;</span><span style="color: #004000;">condition</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'type'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$bundle</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">-&gt;</span><span style="color: #004000;">notExists</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$dest_field</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">-&gt;</span><span style="color: #004000;">exists</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$source_field</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">-&gt;</span><span style="color: #004000;">accessCheck</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">FALSE</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> &nbsp; <span style="color: #000088;">$nids</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$query</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">execute</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> &nbsp; <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'total'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/count"><span style="color: #990000;">count</span></a><span style="color: #009900;">&#40;</span><span style="color: #000088;">$nids</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'ids'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array_chunk"><span style="color: #990000;">array_chunk</span></a><span style="color: #009900;">&#40;</span><span style="color: #000088;">$nids</span><span style="color: #339933;">,</span> <span style="color: #000088;">$nodes_per_batch</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'current'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span> &nbsp; <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'total'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'#finished'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">;</span> <span style="color: #b1b100;">return</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span> &nbsp; <span style="color: #000088;">$nids</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array_shift"><span style="color: #990000;">array_shift</span></a><span style="color: #009900;">&#40;</span><span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'ids'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000088;">$nodes</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$storage</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">loadMultiple</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$nids</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> &nbsp; <span style="color: #666666; font-style: italic;">/* @var $node \Drupal\node\NodeInterface $node */</span> <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$nodes</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$node</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #000088;">$dest_field</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #000088;">$source_field</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// Programmatically copy field data</span> <span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'current'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">++;</span> <span style="color: #009900;">&#125;</span> &nbsp; <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'#finished'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/min"><span style="color: #990000;">min</span></a><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'current'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">/</span> <span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'total'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span></pre></div></pre> <p>In this case, the $sandbox variable is passed by reference to each iteration of the post_update hook. It will loop over this function until $sandbox['#finished'] == 1 — or until the <i>current node operation</i> is equal to the <i>total number of nodes</i>.</p> <p>In my case, I needed to run this operation on a few different fields across half a dozen content types each with their own mappings — I’ll just use <i>article </i>and <i>post</i> in my example below. To make it a little easier, I wrote the custom callback <i>_my_module_copy_field_values</i> to apply updates on a per content type and field basis. Then I called it from a few implementations of <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension%21module.api.php/function/hook_post_update_NAME/8.7.x"><i>hook_post_update_NAME</i></a>, one for each content type.</p> <p>With your custom callback <i>_my_module_copy_field_values</i> defined, you simply call it in your <i>hook_post_update_NAME </i>implementations defined in your MY_MODULE.post_update.php file. Then when you run database updates, your work is done.</p> <pre> <div class="geshifilter"><pre class="php geshifilter-php" style="font-family:monospace;"><span style="color: #009933; font-style: italic;">/** * Migrate Article field_old_one &gt; field_new. */</span> <span style="color: #000000; font-weight: bold;">function</span> my_module_post_update_8001_migrate_article_field<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> _my_module_copy_field_values<span style="color: #009900;">&#40;</span><span style="color: #000088;">$sandbox</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'article'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'field_old_one'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'field_new'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span> <span style="color: #009933; font-style: italic;">/** * Migrate Post field_old_two &gt; field_new. */</span> <span style="color: #000000; font-weight: bold;">function</span> my_module_post_update_8002_migrate_post_field<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$sandbox</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> _my_module_copy_field_values<span style="color: #009900;">&#40;</span><span style="color: #000088;">$sandbox</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'post'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'field_old_two'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'field_new'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span></pre></div></pre> <p>Executing your field level changes in <i>hook_post_update_NAME</i> versus <i>hook_update</i> ensures that any schema level changes (which should be in <i>hook_update</i>) are completed <i>before </i>your content level changes — just in case you’ve got some other updates going on or are working with a handful of other developers in the same codebase.</p> <p>Do you have a simpler way to do this? Feel free to share in the comments.</p> <!-- google doc id: 1-AbHtUFkGxNbel_3VJ7Wf89is-Yc6HqOOaLE3MqdHxE --></div> <a href="/about/john-ferris" hreflang="en">John Ferris</a> Tue, 22 Jun 2021 15:54:23 +0000 John Ferris 10272 at https://atendesigngroup.com 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"><h2>Drupal 7 is nearing end of life (EOL)&nbsp;</h2> <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> <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&nbsp;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> <p><strong>Note:</strong> <em>I added some material to this article after publication regarding the points identified with asterisks<sup>*</sup>, based on feedback from an industry peer. Check it out at <a href="#additional-material">the bottom of the post.</a></em></p> <ul> <li>Cost: Low</li> <li>Build Time: None<sup><a href="#additional-material">*</a></sup></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.<sup><a href="#additional-material">*</a></sup></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>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> <p id="additional-material"><strong>Additional notes for <em>Drupal 7 Vender Extended Support</em>, added June 22, 2021:</strong><br /> <em>"Build time: None"</em> is not strictly accurate - users who choose Drupal 7 ES will probably need to install an update status module to inform their chosen vendor of version data for their core and contributed modules. They will also need to adopt a new update strategy as their module updates will be coming from a new source (their chosen vendor). These steps won't need to be taken until we're closer to Drupal 7 EOL - perhaps the Spring or Summer of 2022.</p> <p><em>"No further action required at this time"</em> may be misleading. Action will be required as we approach Drupal 7 EOL in the Spring or Summer of 2022 - namely choosing an ES vendor and following their instructions.</p></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 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 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/image/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 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 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