<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ryan Robinson</title>
    <description>The latest articles on Forem by Ryan Robinson (@ryanr).</description>
    <link>https://forem.com/ryanr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F695583%2F2bd4dc7a-4cce-48cb-93e2-125cfc551d6c.png</url>
      <title>Forem: Ryan Robinson</title>
      <link>https://forem.com/ryanr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ryanr"/>
    <language>en</language>
    <item>
      <title>Text Underline Position</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 21 Feb 2024 22:43:20 +0000</pubDate>
      <link>https://forem.com/ryanr/text-underline-position-4gl4</link>
      <guid>https://forem.com/ryanr/text-underline-position-4gl4</guid>
      <description>&lt;p&gt;This one is a quick accessibility CSS tip. I have long been a proponent of the old standard of underlining links, and never underlining anything else. It helps make it clear that it is a link. Add a hover and focus effect to make it more obvious when they are selecting it, too.&lt;/p&gt;

&lt;p&gt;But it can introduce a problem: for certain reading disabilities, the line being close up against the text can make it hard to read. Even for myself without one of those disabilities, once it was flagged for me I immediately saw it every time I looked at a link.&lt;/p&gt;

&lt;p&gt;So here's a quick CSS solution that significantly helps.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;text-underline-position: under&lt;/code&gt; to make links with underlines easier to read. It moves the underline down to the bottom of the hanging letters (g, y, j) rather than right up tight against the bottom of other letters and cutting right through the middle of the hanging letters. It may not quite be perfect - even better might be a few pixels below the hanging letters to get space there as well - but it solves the majority of the problem with one quick CSS change.&lt;/p&gt;

</description>
      <category>css</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Drupal Open Menu</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 21 Feb 2024 22:42:45 +0000</pubDate>
      <link>https://forem.com/ryanr/drupal-open-menu-5bik</link>
      <guid>https://forem.com/ryanr/drupal-open-menu-5bik</guid>
      <description>&lt;p&gt;&lt;a href="https://ryanrobinson.technology/websites/drupal/idrc-presentation/"&gt;In a previous related post, I wrote about some lessons in a co-design project of an open menu, with a video where we presented about it to the IDRC&lt;/a&gt;. That was mostly about the process and this is about the result: a header navigation page on a Drupal 9 site. The goal was to provide an overview of the same information that’s in the main navigation, but in a more accessible way for screen readers and with descriptions that provide more information about the page without having to load the page. It was not a complete index of every page on the site; it was only an alternate format for the menu.&lt;/p&gt;

&lt;p&gt;As mentioned in that previous post, an important requirement is that it must update programmatically; we cannot leave it as a separate manually-updated page, because realistically it would only be a matter of time before somebody updated the menu but not the header navigation page.&lt;/p&gt;

&lt;p&gt;There's &lt;a href="https://github.com/ryan-l-robinson/Drupal-open-menu"&gt;a GitPod demo version of my solution on my GitHub&lt;/a&gt;. That’s only the relevant customizations, not a full functioning site, and you'd need to add a couple of menu items to really see it in action.&lt;/p&gt;

&lt;p&gt;Here's an example screenshot of the open menu structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/img/2024/open-menu-example.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/img/2024/open-menu-example.png" alt='"Open Menu page. After an introductory description, there is a sample page at the top level that is an h2 and a sample page below that at an h3."'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Menu Entity Index
&lt;/h2&gt;

&lt;p&gt;If you’re familiar with Drupal, you likely read the introduction and immediately thought of views. Views are the perfect solution whenever you are looking at a scenario involving one data source displayed in multiple ways. Drupal 9 does not expose everything I needed to views on its own, but there is a module that fills in the gap: &lt;a href="https://www.drupal.org/project/menu_entity_index"&gt;Menu Entity Index&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that is installed, you’ll need to configure it. I did not find the settings page intuitive at all, but the first block of settings is which menus need to be indexed and the second block is which types of target to index. In my case, I only want to index content in the main navigation.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/img/2024/menu-entity-index-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/img/2024/menu-entity-index-settings.png" alt='"Settings for menu entity index. The first block is tracked menus and the second is tracked entity types."'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notably, it cannot index views pages, so for this scheme to work, you can’t use views pages in the menu. Fortunately you can still make a views block, put that on a page, and put that page in the menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure the view
&lt;/h2&gt;

&lt;p&gt;The configuration for the view itself won't be too much of a surprise for anybody who has configured views before. It has a grouped block for each second-level menu, where the header had a programmatically-set ID so that they can be used for links (both from that top block and from the top level of the main menu itself). There's a lot of mundane configuration here so I won't break it all down, but &lt;a href="https://github.com/ryan-l-robinson/Drupal-open-menu/blob/main/sync/config/views.view.open_menu.yml"&gt;here's the configuration file&lt;/a&gt; if you want to import to your site, or you can check it out using the GitPod demo and going to Structure -&amp;gt; Views -&amp;gt; Open Menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding the view template
&lt;/h2&gt;

&lt;p&gt;I still had one more problem. The view involved used the grouping function to bunch together the results. I wanted the group label to be an h2 and have an id on it, so I did that using the rewrite the results functions. That worked with no issues.&lt;/p&gt;

&lt;p&gt;But I noticed it was also supplying an empty h3 immediately before my h2. That doesn’t hurt sighted users, but it does complicate the experience for screen reader users. It took some time to realize the h3 was coming from a template file in the views core module: views-view-unformatted.&lt;/p&gt;

&lt;p&gt;Once I knew the source of the original file, I could edit it directly, but that would be lost as soon as there was an update to the theme. The more permanent answer is a subtheme with an override of that file. That seems a little excessive in the case of this demo, but in a real site you'll probably want a custom subtheme for all your CSS and templates anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ryan-l-robinson/Drupal-open-menu/blob/main/web/themes/custom/demo/templates/views/views-view-unformatted.html.twig"&gt;That file is also in the GitHub&lt;/a&gt;, but it was a pretty tiny change removing the h3 wrapper from the grouping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;There are a few caveats to this system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's no handling for menu items more than 2 levels deep. That may well be possible, but we didn't need it, so I haven't included it in this demo.&lt;/li&gt;
&lt;li&gt;No views pages in the menu will show up in the open menu. But view blocks inserted into a page, where the page is in the menu, will show up.&lt;/li&gt;
&lt;li&gt;Be very careful changing the module settings. Perhaps this is fixed two years later, but at the time, attempting to index certain entity types would result in a white screen of death on the next cron run. We've never had problems with indexing content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could add some built-in warnings to the admin interface, or even override admin forms, to try to suppress the possibility of those caveats causing a problem, but this blog post was long enough so I'll leave it here.&lt;/p&gt;

</description>
      <category>drupal</category>
    </item>
    <item>
      <title>IDRC Presentation on Open Menu</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 21 Feb 2024 22:41:47 +0000</pubDate>
      <link>https://forem.com/ryanr/idrc-presentation-on-open-menu-6do</link>
      <guid>https://forem.com/ryanr/idrc-presentation-on-open-menu-6do</guid>
      <description>&lt;p&gt;I recently joined the Inclusive Design Research Centre to present along with a couple of colleagues how we designed an "open menu", a header-focused main menu navigation that works much better for screen reader users and also provides some other benefits like a Ctrl+F find.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fluidproject.atlassian.net/wiki/spaces/fluid/pages/11513504/Community+workshops+and+design+crits"&gt;The video is available on the IDRC wiki.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't have a transcription of the entire presentation, but here are some of my key notes from the perspective of the Drupal developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;The co-design process loop often went something like: Ashley (accessibility consultant) described what worked and what didn't about her experience as a screen reader user, Mark (digital experiences librarian) made an HTML mockup of what we should try instead, and I figured out how to make Drupal do that in the best and most sustainable way. Repeat as necessary until we're all happy with the results.&lt;/p&gt;

&lt;p&gt;I had to consider several issues, not only accessibility. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeline: One bit of important context was the launch timeline. I had just been hired and I was learning my way around the old site at the same time I was beginning the new site. We had a deadline for the new site to launch, what was at the time expected to be the Drupal 7 end of life (it got extended again after that). These menu considerations came as part of this rebuild process. That meant there was some balancing between what is ideal and what can be done in time.&lt;/li&gt;
&lt;li&gt;Security: Failure to meet that timeline would have security implications if we didn't meet it. We could have launched the open menu after launching the new site, in order to hit the end of life deadlines, but it would have been awkward at best and required restructuring the main visual menu at worst.&lt;/li&gt;
&lt;li&gt;Visuals: While not the main focus of this feature, there is visual design for sighted users to consider. We want it to still be attractive and easy to use for sighted users as long as it doesn't come at the cost of screen reader users.&lt;/li&gt;
&lt;li&gt;Maintainability: How do we ensure we keep this working and accurate? A major important point to me was that there needed to be one source of information that fed both the collapsed menu and the open menu. Otherwise, there's a high likelihood that at some point the two will get out of sync with each other, where somebody adds to or rearranges the collapsible menu without considering the open menu or vice versa. Sometimes an inaccurate accessibility feature is worse than no accessibility feature at all, because people wouldn't think to go try the less-ideal collapsible menu if they've been assured they have all the same content.&lt;/li&gt;
&lt;li&gt;Open source models: What can be done in Drupal core, what in a contributed theme, what in a contributed module, what in custom code? If a module is close to what we need but not quite there, how do we navigate what we report and try to get the maintainer to change vs what we should rebuild ourselves?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;This was a fascinating project for me in ways much deeper than the technical details. So here's a few things that stood out to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Empathy vs checklist: The biggest lesson is that personal experiences of disabled people really matter. I'd worked with accessibility before, but mostly at the level of a WCAG (Web Content Accessibility Guidelines) checklist in a contract consultancy context where finishing the job under the budget was the main priority. This whole project was a great example of taking the time to get it truly right to real user experiences. Ashley explained that even though a standard Drupal menu passes the checklist, it can be really time-consuming for her. Hire someone if you can to fill that same role. It opened my eyes to a more emphathetic approach rather than a checklist approach. Maybe you can't hire somebody - most of us can't, and even our time with Ashley was limited - but at least take the time to sit and try to imagine what it would feel like to do this without sight, or without hearing, or without a mouse. Don't just hit the WAVE extension button in your browser and declare it done.&lt;/li&gt;
&lt;li&gt;Multiple solutions: Sometimes there isn't one solution that is perfect for everybody. We tried to figure out a way to make a menu that was optimal for all users. But the reality is that the standard collapsed navigation is ideal, tuned over decades of web development, for most sighted users who can skim at a glance without it taking much cognitive load. So we took an additive approach instead, keeping both versions of the menu. In hindsight this isn't really that shocking - we do the same for things like having a video and a transcript - but I hadn't really thought about it for something as central as the main navigation.&lt;/li&gt;
&lt;li&gt;Jargon: Getting the descriptions for each page was more challenging than expected and one of the reasons why were questions about jargon. Often in talking about accessibility, we talk about wanting the most generic phrasing that everybody understands, avoiding jargon. That's great for new users not familiar with the jargon. But there is a tradeoff for those who are familiar with it: that's probably what they're looking for. If they're looking for the jargon and it isn't in the description, it doesn't show up from a site search or in the open menu items page where a Ctrl+F might find it. Some of that could be mitigated by including the jargon and the broader definition, but that has &lt;a href="https://dev.to/websites/accessibility-descriptive/"&gt;a tradeoff of making the content longer to sift through, which is especially more time consuming on a screen reader&lt;/a&gt;. Making it more accessible to one group can make it less accessible for others.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Coming Soon
&lt;/h2&gt;

&lt;p&gt;I have another post coming about how this was technically achieved, which was relatively straightforward. The bigger lessons were here, in the process, but I do want to share how it was done for those that are interested in replicating it.&lt;/p&gt;

</description>
      <category>a11y</category>
    </item>
    <item>
      <title>Dark Content Mode</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 21 Feb 2024 22:40:37 +0000</pubDate>
      <link>https://forem.com/ryanr/dark-content-mode-4d1d</link>
      <guid>https://forem.com/ryanr/dark-content-mode-4d1d</guid>
      <description>&lt;p&gt;In case you haven't caught it yet, there's new functionality coming to browsers for dark mode applied to content. This could come as a surprise to web developers who did not plan for dark mode in their design, but is overall a win for those of us who prefer dark mode on all the things all the time.&lt;/p&gt;

&lt;p&gt;It's already become my default on Android Edge, inheriting that I have my device in dark mode. I haven't found it to be default on any other browser (including desktop Edge). Chrome has it as an experiment features flag in chrome://flags, so I did most of my testing by enabling that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues Found
&lt;/h2&gt;

&lt;p&gt;The biggest problem I found in my main work site was with images that were previously assuming white backgrounds. I found the nicest solution to that was using an SVG instead of a PNG file, which the dark content mode adapted to invert the colours quite nicely.&lt;/p&gt;

&lt;p&gt;There were also some insufficient contrast I discovered, like grey borders around form text boxes, that were maybe a little weak in light mode but were barely visible in dark mode. Many of these were quickly solvable by making it a stronger contrast. For example, instead of a grey border against the white background, I made it into a black border. It content dark mode, it would flip to a white border on black background. Both modes came out looking better.&lt;/p&gt;

&lt;p&gt;I identified a few other contrast elements that were not quite ideal. But attempting a similar approach did not work. There were a few variations where I had black against the default light theme and trying it with the dark content mode turned it into a grey instead of a white - far from invisible, but not as strong of a contrast as I wanted. This is a new feature, not enabled by default other than that Android Edge that few people use, so I'm hoping this gets cleaned up on the browser end and there isn't much else I need to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Way to Target?
&lt;/h2&gt;

&lt;p&gt;I tried the media query for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"&gt;prefers-color-scheme&lt;/a&gt; but it doesn't help for this. That detects if the device prefers dark mode, not if the browser's dark content mode is on. It's nice if you want to specifically define what everything should look like in dark vs light modes. But as far as I could find, there's no way to specifically target content dark mode at this time.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Composer: Options for a Submodule</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 23:00:39 +0000</pubDate>
      <link>https://forem.com/ryanr/composer-options-for-a-submodule-544h</link>
      <guid>https://forem.com/ryanr/composer-options-for-a-submodule-544h</guid>
      <description>&lt;p&gt;Suppose you want to tie in another private git package, such as something that is not listed on the Drupal directory - as part of your standard build process. Here are two approaches to do that:&lt;/p&gt;

&lt;h2&gt;
  
  
  Composer
&lt;/h2&gt;

&lt;p&gt;Drupal packages are managed with a composer file. Composer files can pull from many sources. Most commonly in the case of a Drupal project, that means pulling in modules and themes from the Drupal directory. But it also includes the ability to pull in projects as zip files or a git project hosted elsewhere, even a private one.&lt;/p&gt;

&lt;p&gt;Here's how:&lt;/p&gt;

&lt;p&gt;Create the module that you want to pull in and give it a composer.json file. Here's an example of that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ryan-robinson/demo-module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drupal-module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Demo module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Drupal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GPL-2.0+"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"drupal/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^9.0 || ^10"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8.1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add two things to the composer.json of your Drupal project:&lt;/p&gt;

&lt;p&gt;One, you need to define the source for where to get the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"repositories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vcs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git@gitlab.com:ryan-robinson/demo-module.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, you need to add it to the requirements list to pull in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ryan-robinson/demo-module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev-main"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the repository is private, you'll also need an auth.json file with your personal access token. Here's an example that would work for a GitLab project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gitlab-token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"gitlab.com"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"token value"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Git Submodule
&lt;/h2&gt;

&lt;p&gt;The other approach is to make it a git submodule.&lt;/p&gt;

&lt;p&gt;The advantage of the submodule approach is if you want to be able to continue to contribute to that. You can work on the parent project and the submodule at the same time, committing them both separately. Of course that only matters I'd you have permission to do so.&lt;/p&gt;

&lt;p&gt;The context I've found myself using this: when I'm developing a custom Drupal module, I need a Docker devcontainer environment to work within for testing. That's one git repository. I also need a repository for the module itself, so that it will be ready to share with other Drupal projects (often using the composer technique above). This allows me to load the devcontainer, work on the module, and commit back to the module.&lt;/p&gt;

&lt;p&gt;This can be done by running the &lt;code&gt;git submodule add&lt;/code&gt; command, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git submodule add &lt;span class="o"&gt;[&lt;/span&gt;URL to module]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a .gitmodules file at the project root that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[submodule "web/modules/custom/demo-module"]
    path = web/modules/custom/demo-module
    url = [URL to module]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>drupal</category>
      <category>composer</category>
      <category>php</category>
    </item>
    <item>
      <title>Accessibility: Levels of Descriptiveness</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:59:46 +0000</pubDate>
      <link>https://forem.com/ryanr/accessibility-levels-of-descriptiveness-55h8</link>
      <guid>https://forem.com/ryanr/accessibility-levels-of-descriptiveness-55h8</guid>
      <description>&lt;p&gt;There's a tension in discussing accessibility for screen reader users that comes up sometimes. Most recently and perhaps most commonly is about ALT text, but it can also apply to other things, too. The main conflict is between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should provide lots of detail in order to make sure a blind user has access to as much detail as a sighted user can. If you've got a picture of a cute cat, you provide all the details about the cat: colour, size, facial expression, posture in the shot, anything that a sighted user would be able to see.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should provide only the core details so that the screen reader user can get to the important point as fast as possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It might be tempting, especially if you're in the first camp, to frame this as something like equality vs efficiency. But it's really two different definitions of equality: is it equality in detail or equality in function.&lt;/p&gt;

&lt;p&gt;Another way to think about it is in terms of cognitive processing time. When an image has more details, it doesn't really add to the time that a sighted user needs to process it all, unless it's a Where's Waldo kind of scenario that is intentionally hiding details in a way that is not available at a glance.&lt;/p&gt;

&lt;p&gt;On the other hand, adding more text description that needs to be spoken out loud is a linear function. If you have twice as much descriptive content, that's twice as much time to listen to it. Maybe you structure it in such a way that they can get the basics out of the way and then have the longer description in a different spot that they can easily skip over, giving them a choice, but that's not always going to be easy.&lt;/p&gt;

&lt;p&gt;So what's the answer? How much description should be added for screenreader users? Like many things in life, I think the answer is "it depends."&lt;/p&gt;

&lt;p&gt;Here's one scenario: you're posting on Mastodon about how cute your cat is. In that kind of scenario, the point is the details. You probably should have a description of the cat and what makes it cute. If somebody is choosing to listen to the description of the image attached to a post about how cute your cat is, it's probably fair to assume that they want the details even if that takes a minute or two.&lt;/p&gt;

&lt;p&gt;A chart of data may vary on the context. In a quick casual social media post, it's probably better to get to the point. Imagine a chart that shows the increase of CO2 emissions over time, spanning several years with lots of data points, but the clear purpose of the chart is to show how badly things are increased. Somebody probably doesn't want to be scrolling social media and get bogged down in a 5 minute narration of data points naming every year plus exact numbers for that year that all blend together and are hard to interpret. That's probably not what they're there for.&lt;/p&gt;

&lt;p&gt;But what if it's the same chart but as an academic resource? If somebody is doing research, they might need to know the exact data points, so the ALT text (or corresponding text nearby) needs to be quite detailed.&lt;/p&gt;

&lt;p&gt;Side note: I once heard about efforts to aid screen reader users encountering a graph to get the point better than a list of numbers, that relies on sounds. If the data is going up, it makes a higher pitch sound. If the data is going down, it makes a lower pitch sound. It will be interesting to see if something like that ever becomes standardized enough to work.&lt;/p&gt;

&lt;p&gt;This exact same distinction can also apply to other context like menu items: do you want to add extensive aria-labels to force verbose experiences on blind users? The title attribute is a nice balance in that example. At least in Jaws, there is a user setting for whether to read out title text. This allows developers to empower the blind users to decide for themselves whether they want the verbose description or not. And it's always better to empower users to have control over their own experience whenever possible. Provide title attributes and if they want it, they'll have it; if they don't want it, it won't slow them down.&lt;/p&gt;

&lt;p&gt;In any case, the main point is that ALT text and accessibility in general is not always an obvious formula. Sometimes there are tensions to navigate. The most important part is that you are thinking about it, and when possible listening to the real experiences of users to steadily improve how to navigate those tensions.&lt;/p&gt;

</description>
      <category>a11y</category>
    </item>
    <item>
      <title>Drupal: OpenAI Module</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:58:48 +0000</pubDate>
      <link>https://forem.com/ryanr/drupal-openai-module-4f36</link>
      <guid>https://forem.com/ryanr/drupal-openai-module-4f36</guid>
      <description>&lt;p&gt;AI, or more specifically large language models (LMMs) are all the rage these days, so it shouldn't come as too much of a surprise that there is now &lt;a href="https://www.drupal.org/project/openai"&gt;a Drupal module that integrates OpenAI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is only an alpha release but includes several submodules to leverage OpenAI tools within a Drupal site in various ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate content for you in any content type&lt;/li&gt;
&lt;li&gt;Speech to text&lt;/li&gt;
&lt;li&gt;Interaction with the ChatGPT prompt endpoint&lt;/li&gt;
&lt;li&gt;A button in CKEditor to send a prompt to OpenAI and get text back&lt;/li&gt;
&lt;li&gt;Assistance in the content editing process, such as adjusting your tone and summarizing body text&lt;/li&gt;
&lt;li&gt;Analyzes logs to summarize likely problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I haven't given it a try yet, mainly because I do not have a paid OpenAI API key and I assume I cannot use this without one, but it definitely could be worthwhile in certain Drupal site contexts.&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Drupal: Paragraphs Sidebar</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:58:09 +0000</pubDate>
      <link>https://forem.com/ryanr/drupal-paragraphs-sidebar-22e2</link>
      <guid>https://forem.com/ryanr/drupal-paragraphs-sidebar-22e2</guid>
      <description>&lt;p&gt;Suppose you want to be able to specify a contact person for most nodes of content on a site, which will then show up in the sidebar with a label and some information about the contact person. Each node could have zero contacts, could have one contact, or could have several contacts.&lt;/p&gt;

&lt;p&gt;I solved this using a combination of &lt;a href="https://www.drupal.org/project/paragraphs"&gt;the Paragraph module&lt;/a&gt;, &lt;a href="https://www.drupal.org/project/profile"&gt;the Profile module&lt;/a&gt;, content type fields, and views. A demo of this is available in &lt;a href="https://github.com/ryan-l-robinson/Drupal-paragraphs-sidebar"&gt;my GitHub&lt;/a&gt; that you can load up using &lt;a href="https://www.gitpod.io/"&gt;GitPod&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the paragraph and its fields
&lt;/h2&gt;

&lt;p&gt;First, create the paragraph and its corresponding fields. In my case, I wanted two fields: a label that will be displayed, and a user reference to connect to a staff user account.&lt;/p&gt;

&lt;p&gt;I created the paragraph type and called it “Contact.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DjZpAxBW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-create-paragraph.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DjZpAxBW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-create-paragraph.PNG" alt='"Creating a paragraph type called Contact"' width="727" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, add a field for the label, as a text select to choose from a few pre-defined options including "Primary Contact," "Secondary Contact," etc. Only allow one label per paragraph, which is different from where we'll later allow multiple paragraphs per node.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iHNFuqbb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-create-label-field.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iHNFuqbb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-create-label-field.PNG" alt='"Creating a field called Contact Label, as a Text (List) type"' width="621" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dPF6YzSb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-label-settings.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dPF6YzSb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-label-settings.PNG" alt='"Settings for the label field, including the valid options and to allow allow 1 value."' width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, add a field for the user reference. Set this up to show up as an autocomplete, filtering to only show those who are of the Staff role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D9gDKiIa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-create-user-field.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D9gDKiIa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-create-user-field.PNG" alt='"Creating a user entity reference field called Contact User"' width="626" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WHA87-jC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-user-filter.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WHA87-jC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-user-filter.PNG" alt='"The user entity reference settings, filtering to only show those of the role Staff"' width="541" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Assign to a content type
&lt;/h2&gt;

&lt;p&gt;Add a field on the content type that references this paragraph. The field type is going to be an entity reference revision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Vt7yrHO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-add-field-to-content-type.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Vt7yrHO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-add-field-to-content-type.PNG" alt='"Adding the paragraph reference to the Page content type"' width="625" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C9CRIHS6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-entity-reference-paragraph-type.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C9CRIHS6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-entity-reference-paragraph-type.PNG" alt='"The settings for the entity field, filtering to be able to select paragraphs of type Contact"' width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, since it's going to display it in the sidebar using a view, also remove it from the display for the content type. I won't get into details of designing the page on this post.&lt;/p&gt;

&lt;p&gt;This step can be repeated to add the field to multiple content types, so that you can have the consistent design on all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the profile
&lt;/h2&gt;

&lt;p&gt;For the sake of this demo, I'll only add a couple of fields to the profile - a name, a phone number, and an email - and I won't do any of the design of the profile page, just creating the fields for the purpose of showing it in the sidebar of the content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EUtFfU65--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-profile-fields.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EUtFfU65--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-profile-fields.PNG" alt='"Created fields for the profile: name, phone, and email"' width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create sidebar view
&lt;/h2&gt;

&lt;p&gt;That's all the underlying structure. We can now proceed to creating the view itself. This will be a view of content, because while it will be showing paragraphs, it will be showing those paragraphs based on the content being viewed. It will be a block, since we're going to want blocks that can be placed in theme block regions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2spcsKZl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-start-view.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2spcsKZl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/Paragraphs-sidebar-start-view.PNG" alt='"Settings for creating the view. The essential component is the view settings showing Content."' width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, make a contextual filter so it will only show content related to the current content on display. This will be on the node ID of the page being viewed, so that only paragraphs related to the current node will come up as results. Create a filter for content ID and set the default to be "Content ID from URL."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KWSlU02b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-contextual-filter.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KWSlU02b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-contextual-filter.jpg" alt='"Settings for the contextual filter, the content ID set to default on the content ID in the URL."' width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to create the link between the page being viewed and its contact paragraphs. This can be done with the relationships functionality of views. Under Relationships, click on Add, then select "Paragraph referenced from field_contact."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5GrfJOIe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-relationship.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5GrfJOIe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-relationship.jpg" alt='"Adding a relationship to paragraph referenced from field_contact."' width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We then need two more relationships:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;From the contact field of the paragraph back to the user: "User referenced from field_contact_user."&lt;/li&gt;
&lt;li&gt;From the user to the profile, which will have the majority of the fields we want to display: "Profile."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last setting we'll change in the Advanced section is near the bottom: set "Hide block if the view output is empty" to Yes. We don't want it to show up at all if there isn't a contact for the current content.&lt;/p&gt;

&lt;p&gt;Now that we've built the complexities of the query, we can return to the more common configuration in the left pane to determine what is shown. I set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display for the view to an unformatted list of fields.&lt;/li&gt;
&lt;li&gt;The fields are the contact name, the contact label, the phone number, and the email address, all from the associated profile. Each field is set to be hidden if there is no result, in case a profile is created with some information but not others, e.g. somebody offers their email but doesn't want to share their phone. You can also add more styling around how these get displayed (labels, links, etc).&lt;/li&gt;
&lt;li&gt;Filter on the paragraph type to be "Contact," on the paragraph to be published, and for the user to be active. This helps avoid unexpected results when a user is later blocked, or somehow another paragraph type is associated to the content.&lt;/li&gt;
&lt;li&gt;Sort by the contact label and then by contact name, which will be more intuitive to users than sorting on something else like authored date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can go back to the settings on the Unformmated list to group results by the label, so that the label will show up as a header. If you want more precise control over what HTML tags are used for which elements, &lt;a href="https://www.drupal.org/project/semanticviews"&gt;semantic views&lt;/a&gt; is another great module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Place block view in sidebar
&lt;/h2&gt;

&lt;p&gt;Finally, add the block to show in that sidebar for all relevant content types. Of course this will depend on your theme having a sidebar region, but most have at least one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--faDfxVkw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-block.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--faDfxVkw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-block.jpg" alt='"Adding a block to appear in the sidebar of the Page content type"' width="700" height="840"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final result
&lt;/h2&gt;

&lt;p&gt;Here's an example of what all of this looks like, at its most basic level without any extra CSS styles added:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TB8y0UNN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-demo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TB8y0UNN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/paragraphs-sidebar-demo.jpg" alt='"Sidebar in place on a demo page, showing the Primary Contact label, name Ryan Robinson linked to the profile, email address, and phone number as a valid phone link."' width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>drupal</category>
    </item>
    <item>
      <title>Drupal: Recycle Bin</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:56:23 +0000</pubDate>
      <link>https://forem.com/ryanr/drupal-recycle-bin-4aog</link>
      <guid>https://forem.com/ryanr/drupal-recycle-bin-4aog</guid>
      <description>&lt;p&gt;Drupal does not come with any kind of recycle bin / trash system out of the box, unlike the other technologies I have worked with most (WordPress, SharePoint). There were a few modules available at the time I looked into this (admittedly over a year ago now), but they were all rough, either old or barely developed. None were reliable enough to count on. In this post I’ll describe a simple halfway approach that did not require any extra code.&lt;/p&gt;

&lt;p&gt;There is a demo of this &lt;a href="https://github.com/ryan-l-robinson/Drupal-recycle-bin"&gt;available on my GitHub&lt;/a&gt;, with a GitPod container to test it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions: Only Admins can Delete
&lt;/h2&gt;

&lt;p&gt;Check permissions for each content type to confirm that only admins can delete anything. Everybody else will only be able to use the flag field we're about to create to request deletion, not directly delete anything.&lt;/p&gt;

&lt;p&gt;Go to the node permissions screen at /admin/people/permissions. Look for the permissions of each content type that are "Delete own content" or "Delete any content." For the purposes of this demo, only the admin users should have these permissions. Your permission structure may be more complicated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RdO8NY8I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinPermissions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RdO8NY8I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinPermissions.png" alt='"Screenshot of the permissions screen, with all permissions given to admins and no permissions given to others. The key for this example is that only admins have the Delete permissions"' width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Field: Flag for Delete
&lt;/h2&gt;

&lt;p&gt;Proceed to the Manage Fields page of the first content type that you want to have this feature on. For example, if my content type is recyclable_content_type, I would go to /admin/structure/types/manage/recyclable_content_type_/fields. Create a boolean field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RJwE-D8K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinAddField.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RJwE-D8K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinAddField.png" alt='"Screenshot of the form to create a new field. A new field of type Boolean is being created with the label of Flag for Delete."' width="712" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Limit the field to only allow one value at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cLDxlqZv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinFieldStorage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cLDxlqZv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinFieldStorage.png" alt='"Screenshot of the form for setting storage on the field. This field allows only 1 value."' width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final screen for creating the field allows you to add more options that are mostly about helping your content creators understand what this is doing. I'll add some help text as well as change the On and Off labels to be more clear of what they mean.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D0yaaz1x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinFieldHelp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D0yaaz1x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinFieldHelp.png" alt='"Flag for Delete field settings. This includes setting the help text to explain to the users what it does, changing the On label to Delete and the Off label to Keep."' width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You now have the field on this content type ready to use. You may also want to go into "Manage form display" and "Manage display" to customize how this appears for your users. In my case, on the form I put it inside a Details sidebar paired with the published option. I then used a module like &lt;a href="https://www.drupal.org/project/condition_field"&gt;condition_field&lt;/a&gt; to only show that option when the node is already marked as unpublished, so that somebody cannot accidentally mark something for deletion without unpublishing it first. Finally, on the display I hid it entirely because the public should never see that field - it is only for back-end processes.&lt;/p&gt;

&lt;p&gt;If you have more than one content type you want to deploy this feature on, go to the next content type and add a field again. This time, select from the dropdown of existing fields instead of creating a new one. The final steps for help text, form display, and public display remain the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  View: Ready for Deletion
&lt;/h2&gt;

&lt;p&gt;Finally, you're going to want a way that admins can review the nodes flagged for delete and go ahead with permanently deleting after the set amount of time. This is a great problem to be solved by views.&lt;/p&gt;

&lt;p&gt;You can either create a new view or add a new display to an existing view. I'm going to start by building on top of the default admin view named "Content" since I want something that is basically the same but with extra filters. This is at /admin/structure/views/view/content. Either add a new page from the Add button, or duplicate the existing page with the Duplicate Page option in the dropdown near the right side of the screen. If there was only one display which is also the default, the result would be essentially the same.&lt;/p&gt;

&lt;p&gt;I put it in the menu under Content of the Admin menu, and with the path /admin/content/delete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cs7Ew1ye--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewPageSettings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cs7Ew1ye--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewPageSettings.png" alt='"First settings show the path set to /admin/content/delete. Second setting shows the Menu link as Normal and labelled Ready for Deletion."' width="540" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Flag for Delete filter
&lt;/h3&gt;

&lt;p&gt;Add a filter to the view, overriding this page only, on the Flag for Delete field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kVKkJkIb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinAddFlagFilter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kVKkJkIb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinAddFlagFilter.png" alt='"Screenshot of the views admin to add a filter. A filter on the field Flag for Delete is being added."' width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SxU7Ksy4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewConfigureFilter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SxU7Ksy4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewConfigureFilter.png" alt='"Screenshot of the views admin, setting the value of Flag for Delete is equal to True."' width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Published filter
&lt;/h3&gt;

&lt;p&gt;The view I duplicated from already had a Published filter, but it was exposed to allow users to select. So I'm going to edit that one to only show unpublished content. I only want unpublished content, because if somebody has flagged for delete but it is still published, that suggests they might have made a mistake.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TmRR7ggs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewPublishedFilter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TmRR7ggs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewPublishedFilter.png" alt='"Configuration for the Published filter on the view, set as is equal to Published status of No."' width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Date Updated filter
&lt;/h3&gt;

&lt;p&gt;Finally, in my case, I also wanted to filter on the date updated, so that only content that hasn't been updated within the past 30 days would show up. Anything less than that suggests that the content creator may be having second thoughts and isn't really ready to delete it yet. In your context, you may not want this delay at all, or you may want a much longer delay.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2CTtVQTw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewAddUpdatedFilter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2CTtVQTw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewAddUpdatedFilter.png" alt='"Screenshot of adding the Updated date filter to the view"' width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--srhIBeaS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewConfigureUpdatedFilter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--srhIBeaS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ryanrobinson.technology/assets/img/2023/03/RecycleBinViewConfigureUpdatedFilter.png" alt='"Screenshot of settings for the Updated filter, setting the operator to Is Less Than and the value as an offset of -30 days"' width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now admins can periodically check and bulk delete any content that is flagged for delete, unpublished, and last updated more than 30 days ago. You can also do any of the usual views changes from here as you desire, including adding views, changing text to show when there are no results, adding CSS classes, etc.&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>programming</category>
    </item>
    <item>
      <title>Drupal: Include Default View in Module</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:54:42 +0000</pubDate>
      <link>https://forem.com/ryanr/drupal-include-default-view-in-module-1i19</link>
      <guid>https://forem.com/ryanr/drupal-include-default-view-in-module-1i19</guid>
      <description>&lt;p&gt;You may be working on a Drupal module and want to include a default view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;This is actually quite simple: put the configuration file in the config/install folder within the module, with the proper configuration name, and it will install.&lt;/p&gt;

&lt;p&gt;This would be the full configuration file, the same as you would generate using the configuration sync tools.&lt;br&gt;
You can write this file from scratch, especially if they're hidden options. If it is something that has a user interface, you can do what I tend to do: determine all your preferred settings and test it out within the interface, then export the configuration from either the UI (/admin/config/development/configuration/single/export) or drush (&lt;code&gt;drush config-export&lt;/code&gt; but that does all configuration, not only the specific one you want).&lt;/p&gt;
&lt;h2&gt;
  
  
  Uninstall
&lt;/h2&gt;

&lt;p&gt;What about uninstalling the configuration you've installed with the module? This will depend on the context of your module, but you may want to also uninstall this configuration when the module is uninstalled. The configuration might no longer make sense without the module, or you might create conflicts if you installed it once, uninstalled the module but left the configuration, then tried to install it again.&lt;/p&gt;

&lt;p&gt;To do that, add a few lines like this to the uninstall hook of the .module file, for example with a config file named webform.webform.event_registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Implements hook_uninstall
 * */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;module_name_uninstall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Drupal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEditable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'webform.webform.event_registration'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;\Drupal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEditable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'webform.webform.event_registration'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>drupal</category>
      <category>php</category>
    </item>
    <item>
      <title>Brandwood Accessibility Checker</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:54:01 +0000</pubDate>
      <link>https://forem.com/ryanr/brandwood-accessibility-checker-42gn</link>
      <guid>https://forem.com/ryanr/brandwood-accessibility-checker-42gn</guid>
      <description>&lt;p&gt;Today I learned about an interesting tool: the &lt;a href="http://brandwood.com/a11y/"&gt;Brandwood Accessibility Checker&lt;/a&gt;. There are lots of tools out there for testing colour contrast and whether it meets the necessary levels to meet accessibility requirements. But in most of those, you enter one colour for the foreground and one for the background and it gives you the contrast ratio and whether that passes.&lt;/p&gt;

&lt;p&gt;This one goes a step further and lets you see contrast of text in front of images. This is great for scenarios like a homepage slideshow (slideshows often aren't great for accessibility in other ways, but we'll ignore that for now) where there is text of a title that shows up layered in front of an image. Whether the text is readable with enough contrast might depend on what part of the image the text appears over, so this tool lets you specify your image, your text colour, and lets you move the text around to see what the contrast is against the image at that specific point.&lt;/p&gt;

&lt;p&gt;Note that depending on your mobile responsiveness handling and the possibility for other tools like browsers increasing or decreasing the font size, this might still not be a perfect conclusion. The simplest solution when possible is to put the text above or below the image instead of layered on top of it. But if you need to do it, or if you're looking for a tool to help with a more fixed design scenario - like a PDF document or printed poster - this is a nice tool to have in your belt.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Drupal: Disable Password Reset</title>
      <dc:creator>Ryan Robinson</dc:creator>
      <pubDate>Wed, 03 Jan 2024 22:52:41 +0000</pubDate>
      <link>https://forem.com/ryanr/drupal-disable-password-reset-5cna</link>
      <guid>https://forem.com/ryanr/drupal-disable-password-reset-5cna</guid>
      <description>&lt;p&gt;In one particular site, we chose to block the user registration and user password reset options. The site is using SSO login anyway, so this would really only impact our development team's superuser admin account (user 1) that is typically blocked outside of an emergency, so this seals up a possible attack vector with no user impact.&lt;/p&gt;

&lt;p&gt;User registration can also be blocked with site configuration, so I wouldn't have bothered with custom code for only that part. But since I was doing it for the password reset anyway, I decided to go ahead with the extra layer of protection on the registration route as well.&lt;/p&gt;

&lt;p&gt;I achieved this with a simple custom module which you can view in full &lt;a href="https://github.com/ryan-l-robinson/Drupal-disable-password-reset"&gt;in my GitHub&lt;/a&gt;. Here's the key part, blocking access to the routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;alterRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RouteCollection&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Always deny access to unwanted routes.&lt;/span&gt;
    &lt;span class="nv"&gt;$disallow_routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'user.register'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'user.pass'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$disallow_routes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$disallow_route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$disallow_route&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$route&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setRequirement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_access'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'FALSE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>drupal</category>
      <category>php</category>
    </item>
  </channel>
</rss>
