Blog en Upload media inline in WYSIWYG editor in modern Drupal 10+ with CKEditor5 <div class="flow_middle"> <p>From a new <a href="">Drutopia member</a> and <a href="">Agaric consulting client</a>:</p> <blockquote> <p>My last question of the day I think. Thanks for all your help!</p> <p>I've been trying to understand the interaction between the media library and using photos within text on pages. If I upload a photo to the media library, I cannot figure out how to embed it in a text block. And vice versa, if I use the text editor to add a photo, I can get it to wrap with text but it won't show up in the media library.</p> <p>It seems like the best practice is that we should try to use the media manager rather than just drop in photos using the text editor method (which doesn't seem to get the photos into the media library), but how do we get the photos in the media library to be able to wrap with text and not be visually separate content?</p> <p>(The help pages I've found on this issue seem outdated, other than the useful observation "Key to this issue [of overlapping media management systems] is that Drupal ships with several features that are similar to Media. The ability to upload files and images is not replaced by Media, but rather supplemented. This means that the user is left with both options (if not more) and must somehow know which one is the built in (not to be used), and new (preferred method)." at <a href=""></a> )</p> </blockquote> <p>We wrote back:</p> <p>Hi!</p> <p>You are absolutely right that "This all seems to be an overwhelmingly large number of difficult steps just to have an image browser with the editor", as a comment you might have run across put it.</p> <p>But a large part of the reason why you are running into confusing instructions is that modern Drupal has it all in core and it is significantly easier! That's why there are so many old posts, including from Drupal 7 but also from modern Drupal before Media Library went into core at some point in Drupal 8, and no clear explanation of how things should be done now in Drupal 10 which now has CKEditor5 replacing CKEditor4 in core.</p> <p>As long as Media and Media Library modules are installed, "all" you need to do is:</p> <ul><li>Go to Administration » Configuration » Content authoring » Text formats and editors: <a href=""></a> </li> <li>Press Configure on the relevant text format (<code>/admin/config/content/formats/manage/basic_html</code>)</li> <li>Drag the Media Library icon (<code>/core/modules/ckeditor5/icons/medialibrary.svg</code>) from <strong>Available buttons</strong> into the <strong>Active toolbar</strong>.</li> </ul><p>And that is how you are able to insert from your media library in CKEditor5 (or add to your media library while inserting a new image with your CKEditor <abbr title="What You See Is What You Get">WYSIWYG) in Drupal 8, 9, 10, 11 and probably 12 and onward too!</abbr></p> <p>We are making this the default behavior in Drutopia as we continually upgrade it with Drupal's latest features and its own unique improvements.</p> </div> 2023 December 22 Benjamin Melançon, Keegan Rankin Display lists naturally with the In Other Words module for Drupal <div class="flow_middle"> <p>It is common for a Drupal site to list multiple items. It could be several authors of a single article, the days a recreation center is open, or the flavors an ice cream parlor serves. Clean, structured data is a strong point of Drupal, but the display of that structured content is limited out of the box. That is why <a href="">DevCollaborative</a> partnered with the <a href="">Agaric Tech Collective</a> to complete a stable release of <a href="">In Other Words</a>, a Drupal module that gives site builders the power to configure precise and natural ways to display lists of items.</p> </div> , <div class="flow_middle"> <h3>Punctuating Lists</h3> <p>If an article has multiple authors (using a user reference field or a content reference field), Drupal core displays each on their own line.</p> <p>Like this ugly, rigid presentation:</p> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2023-06/display-lists-naturally-authors-cropped.png?itok=HcMwopHt" width="946" height="151" alt="A screenshot shows whitespace surrounding left-aligned text, with a bold label &quot;Author(s):&quot; in a left-hand column and in a right-hand column &quot;Clayton Dewey&quot; on one line, &quot;Ben Melançon&quot;, on the next line, and &quot;David Valdez&quot; on a third line." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <p>Those <em>could</em> be separated by a comma using CSS. For example, "By Clayton Dewey, Ben Melançon, David Valdez."</p> <p>A more natural display, though, would be "By Clayton Dewey, Ben Melançon, and David Valdez." If, instead of three people, two people wrote the article (as is the case), we would want to display "By Clayton Dewey and Ben Melançon."</p> <p>Before, a sitebuilder would have to abandon structured content for a text field which an editor would use instead. In other words, instead of using a reference field to display the authors (even if present on the edit form), the sitebuilder would add a regular text field for the content editor to write out the list in a more human-friendly way and that is what would be output, or the content editor would write the byline into the body field.</p> <p>Now, displaying lists of structured content naturally is possible with the <a href="">In Other Words</a> module.</p> <p>(More featureful listings are also possible with Twig templates, but that does not empower sitebuilders— it bogs down themers!)</p> <p>For text list fields and entity reference fields, In Other Words provides the <em>In Other Words: List</em> field formatter. Its settings page looks like this:</p> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2023-06/in-other-words-list-configure-form.png?itok=kQCultfr" width="1300" height="548" alt="A screenshot of the Format configuration for a field set to In other words: List. The options are what are described in this post, such as &quot;Join symbol or separator between list items&quot;, &quot;Final join word&quot;, &quot;Connect final items with a series comma where applicable&quot;, &quot;Text before&quot;, and &quot;Text after&quot; with some additional detail, as well as the option to &quot;Link label to the referenced entity&quot; is checked. It ends with the text &quot;Want more options? Open issues in the issue queue&quot; and buttons to Update or Cancel." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <p>(Technically, there is one formatter each for text lists and for entity references, but all the options which In Other Words adds are the same for both.)</p> <p>We can configure a separator between list items, a final join word, and whether to connect the final items with a comma (or semicolon or any other punctuation we can imagine for our list).</p> <p>In all its glory in context:</p> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2023-06/display-lists-naturally-authors-as-oxford-list.png?itok=GcxZa4hH" width="931" height="305" alt="A screenshot shows the headline &quot;Display Lists Naturally with the In Other Words Drupal Module&quot;, some whitespace, and then as a single line &quot;By Clayton Dewey, Ben Melançon, and David Valdez&quot;. Below that after another couple lines of whitespace one line of regular text beginning the article is visible." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <p>We could also choose not to connect the final items with a comma, if we were ignoramuses unaware of the Oxford comma. In this case for our example we would see "By Clayton Dewey, Ben Melançon and David Valdez." We are aware, however, so we would enable the series comma option in the In Other Words formatter configuration.</p> <p><em>Editor's note: The extreme partisanship in favor of the <a href="">serial comma</a> is all that Ben contributed to this blog post, but this same unreasoning fervor contributed to starting the module in the first place and creating a <a href="">PHP package for making lists using the Oxford comma</a>, which the module relies upon.</em></p> <p>If we want text before or after, that is also an option. In this case, preceding the authors with "By " is a nice way to denote attribution.</p> <p>It is worth noting, though, that In Other Words module can count! To display different preceding or following text if only one item is present, put the plural version first and then a | (pipe symbol) and the singular version. We could have configured the field display to hide the "Author(s)" label and configured the field format to have <strong>Text before</strong> of "Authors: |Author: " if we wanted to keep a more regimented style while including the nicety of precise labels. This feature can also be used for subject-verb agreement, "Jim and Steph are the authors" and "Steph is the author" could be achieved by configuring the <strong>Text after</strong> to be " are the authors| is the author". (This is a place where we could improve how intuitive the interface is, for sure.)</p> </div> , <div class="flow_middle"> <h3>Shortening Sequential Lists</h3> <p>In Other Words can also shorten sequential lists by skipping over the items in the middle of the sequence. For example, if the following full list is available:</p> <p>Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday</p> <p>And a content author selects the following from them:</p> <p>Monday, Tuesday, Wednesday, Thursday</p> <p>The <em>In other words: Sequential terms</em> formatter can be configured to interpret this and output:</p> <p><strong>Monday through Thursday.</strong></p> <p>It even accounts for breaks between sequential items. For example:</p> <p><strong>Monday through Thursday and Saturday.</strong></p> <p>And if you want to get real fancy, you can group terms together under a single label, such as interpreting Saturdays and Sundays as "the weekend".</p> <p>Finally, you can have In Other Words output a single phrase if all items are selected. For example, "All Days".</p> <p>Do so by using the <em>In Other Words: Sequential terms</em> field formatter, which supports term reference fields.</p> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2023-06/days-of-operation-sequential-terms.png?itok=EpqLCUeu" width="1300" height="934" alt="A screenshot of a field&#039;s Format settings for In other words: Sequential terms. The options are what are described in this post, such as &quot;Connecting word&quot;, &quot;Minimum number of items in a sequence before removing middle items&quot;, &quot;All items text&quot;, and &quot;Treat as one label&quot; as well as all the options previously described for In other words: List (Join symbol, Final join word, surrounding text, etc). It ends with &quot;Want more options? Open issues in the issue queue&quot; and buttons to Update or Cancel." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <p>This same functionality is theoretically possible for other list fields, but In Other Words does <em>not</em> currently support summarizing text and numeric lists. We have opened <a href="">a feature request to do so</a>, but do not currently have the need ourselves, so we welcome your involvement. Feel free to open issues about things you think would make the module better, too!</p> <p>A final note. We went through considerable effort to allow In Other Words' conjoining symbols and words, as well as surrounding text, to appear immediately before or after the items in a list with no whitespace. (Note: This does not work when Twig debugging is enabled.) This means you can have information presented as a sentence that ends with a period, "Open Tuesday–Friday." It also means that if you want spacing you need to ensure you add it <em>as part of</em> your join symbol or separator, your final join word, and your before and after text. This is a common gotcha in configuring In Other Words field formatters that you, perhaps, can now avoid!</p> </div> , <div class="flow_middle"> <h3>Conclusion</h3> <p>With the In Other Words module, it is now possible to keep your structured content and display it naturally. The logic of how a list should display in various scenarios is boiled down to an intuitive user interface for site builders, with no coding required. Try it for yourself! Get the module at <a href=""></a>.</p> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2023-06/word-as-image_0.jpg?itok=sPn46Fin" width="550" height="450" alt="A large lower case letter i surrounded by tiny letters, that would spell out &quot;capitalism&quot; if they weren&#039;t crushed and falling over from the first i." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <p><small><a href="">"Word as Image"</a> by Ji Lee is licensed under <a href=";atype=html">CC BY-NC 4.0</a>.</small></p> </div> 2023 June 15 Clayton Dewey, Benjamin Melançon Did the Internet make you a criminal? How can it be proven? <div class="flow_middle"> <p>Quote: "I feel like we are part of the modern day underground railroad attempting to shuttle people to safety when they do not want to leave the party where all their friends are. Divesting is a process and not something you do in one move, you can approach it in small ways."</p> <p>Micky is a worker-owner of <a href="">Agaric Technology Collective</a> and at the intersection of Privacy, Cooperative Software Development and Surveillance Capitalism. She has been an elected member of the board with <a href="">May First Technology Movement</a> for several years and a passionate advocate for free software and online privacy. As the founder of <a href=""></a>, her current focus is on communications technology using BigBlueButton video chat, a platform similar to Zoom, but different in that BBB respects your privacy and does not interact with Google or big tech companies. She been instrumental working with civic groups to change laws that violate our human rights (See Culley vs. Marshall: amicus brief links below)  Her dedication to user privacy has made a significant impact in fostering secure online communities and helping people form or join cooperatives. Micky's approach continues to inspire others to prioritize privacy and collaboration in the digital world.</p> <p> </p> <p>Simply put - You can fall into a criminal category just by visiting a website/forum or being at a location that a criminal has visited at the same time.</p> <p> </p> <h2>Google has ONE off switch for you. </h2> <p> </p> <p>If all of your documents are stored on Google servers, or on any corporation's servers, it gives them access to delete all of your files at anytime and block your access to recover them. How many people know all of the email addresses and phone numbers of family and friends?  Not many if any. The best solution to prevent this is to have your tools, services and information hosted by companies or cooperative entities that share your values. That is why Agaric has chosen May First as our host for many years.</p> <p>Diversify your tools and services. <a href=""></a> offers a suite of free software tools that respect your privacy.<br /><a href=""></a> builds and customizes fee software, web sites, applications and data management.</p> <p> </p> <h3><strong>What is Free Software?  <a href="">Why do we need Free Software?</a></strong></h3> <p> </p> <p><strong>Here are some of the tools, methods and policies that can be used to target you as a criminal today --</strong></p> <p><a href="">Predictive Policing</a> is a dangerous method of using statistics to determine if you 'might' commit a crime in the future :</p> <p>Facial Recognition - how can it fail us?:<br /> Is the <a href="">iPhone X's Facial Recognition</a> Twin Compatible?</p> <p> </p> <p>Shotspotter: Minneapolis schools secretly partnered with <a href="">ShotSpotter</a> surveillance company.</p> <p> </p> <p><a href="">Geofencing</a>: Creepy or cool, privacy experts are concerned  about Geofencing. Companies are putting up invisible electronic barriers that divulge your location when you enter or go through the perimeter  of the electronic 'fence' - using GPS and other tools they can determine your exact location at anytime.</p> <p> </p> <p><a href="">Fusion centers</a> where all of our information is aggregated to be used in court cases to convict you of crimes you may not be aware of.</p> <p> </p> <p><a href="">BlueLeaks</a>: San Diego fusion center's records hacked, posted online.</p> <p><a href="">For Black drivers, a police officer's first 45 words are a portent of what's to come</a>...<br /> A Black driver is more likely to face being searched, handcuffed, or arrested when a police officer's first words are commands rather than a greeting or an explanation.<br />  </p> <p>Meet the Robo Cops | <a href="">Law Enforcement's AI and law enforcement</a></p> <p>RICO Laws are being used to convict large groups of people that are sometimes tangentially linked to actual criminals&gt; Music industry<br /><a href="">Self-snitching</a></p> <h2><strong>Solutions ---</strong></h2> <p>Work locally think and when possible act globally - if it is happening there it will be happening here sooner than later. </p> <p>Text messaging: <a href="">Signal Messenger</a> or <a href="">Telegram Messenger</a><br /> Video chat platform: <a href="">BigBlueButton</a> and the <a href="">BigBlueButton issue queue</a></p> <p> </p> <p>Agaric's BLOG - <a href="">Daily Business Operations Using Free Software</a></p> <p> </p> <p>Real life solutions to stay safer at live events: <a href="">ActSecure</a></p> <p> </p> <p>Where to Find Free Software - <a href=""></a> and <a href="">Alternative to</a>.</p> <p> </p> <p><a href="">Robert Braxman's channel </a>with many videos on staying safer online.</p> <p> </p> <p>As a member of <a href="">Restore the Fourth</a>, Micky is working to protect 4th amendment rights and we submitted a brief urging the SCOTUS to accept a civil asset forfeiture case, Culley v. Marshall, and they did! There is only a 2% chance of any given case being accepted. <br /> Links:<br /><a href="">Culley v. Marshall</a><br /><a href="">What is civil asset forfeiture?</a>  </p> <p>Our next work is on abolishing Qualified Immunity for police and agents of "authority"  -  <strong><a href="">What is Qualified Immunity?</a></strong></p> <p>See Micky speak at a <a href="">webinar</a> hosted by the Center for Global Justice in June 2023.</p> <h2><strong>Other Links of Interest:</strong></h2> <p><strong><a href="">IBM's Watson</a></strong></p> <p><a href=";cn-reloaded=1">Lethal Autonomous Weapons Pledge - Future of Life Institute</a></p> <p><a href=" ">Google's Code Of Ethics For AI Bans Its Use In Weaponry</a></p> <p><a href=" ">Establishing an AI code of ethics will be harder than people think - MIT Technology Review</a></p> <p><a href=" ">Elon Musk Trolls Zuckerberg: "You Don't Understand How A.I. Works"</a></p> <p><a href="">We Won't Build Killer AI Weapons: Elon Musk And Deep Mind Founders Take Oath</a></p> <p><a href="">Google Employees Resign in Protest Against Pentagon Contract</a></p> <p><a href=" ">Microsoft's Ethical Reckoning Is Here | WIRED</a></p> <p><a href="">Microsoft Calls For Federal Regulation of Facial Recognition | WIRED</a></p> <p><a href="Technical standard - Wikipedia"> </a><br /><a href="">OpenCog - Wikipedia</a></p> <p><a href="">Bill Hibbard - Wikipedia</a></p> <p><a href=",_accountability,_and_open_source">Ethics of artificial intelligence - Wikipedia</a></p> <p><a href="">As an example of personality attributes one can assess already using </a><a href="">watson</a><a href=""> AI and twitter feeds - free course -</a></p> </div> 2023 June 07 Michele Metts Always run the desired PHP version when your hosting solution has multiple versions available <section data-headerbg="transparent" class="hero is-cinnamon hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2023-02/elephant-1_0.jpg?itok=GM8PU5D7" width="433" height="650" alt="A black and white photograph of an elephant&#039;s head." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Always run the desired PHP version when your hosting solution has multiple versions available </h1> <h2 class="subtitle">"Oh, you wanted *that* PHP?" </h2> </div> </div> </section> , <div class="flow_middle"> <p>At Agaric, we perform a lot of Drupal upgrades. These very often involve transitioning away from older versions of PHP. Even when your hosting service provides multiple versions of PHP, you can still run into issues activating the appropriate one for each site: whether that's within the web server, at the command line, or via Drush (or other tools). In this blog post, we'll be providing remedies for all of these cases. Most content applies to any PHP application.</p> </div> , <div class="flow_middle"> <p>Only Section 3 is particularly Drupal/Drush specific, but still might give you a hint about running remote commands that use ssh from behind their shiny CLI.</p> <h2>TLDR: just give me what works, NOW (please?)</h2> <p>Since you asked so politely: if you are already familiar with hosting options, bash shell configuration, and Drush, this somewhat lengthy article can be summed up quite quickly, really. For those that might not be pro's in any one of these, you can take this as the quick intro, and keep reading to learn why these situations exist, as well as the detailed fix.</p> <ol><li>If you are on a hosting provider, they normally will have a UI to select the desired PHP version. If you are doing your own hosting, install Ondrej Sury's PHP PPA/Apt repo, and install PHP-FPM (not mod_php). Adjust your SetHandler (Apache), or fastcgi_pass (NGINX) in each site configuration to the socket for the pool of the version of PHP you want.</li> <li>Configure a local ~/bin folder and place a link to the php version you want (e.g. ln -s /usr/bin/php8.1 ~/bin/php) there. Be certain ~/bin is also in your PATH.</li> <li>When using Drush aliases to run commands on remote instances, this still won't work until you add an env-vars: key to your site.yml file and place a PATH in there.</li> </ol><p>Jump to the "Part" below if you only need help with one or more of these.</p> <h2>Part 1: All things hosting (well, at least Apache or NGINX, plus FPM)</h2> <p>Let's start where most people do: setting up the hosting itself. In most cases, a managed hosting provider will have some way to select the appropriate version of PHP for your individual site. If that's the case - pick your target, and you should be good to go (move on to Part 2, you lucky dog)! If you do your own hosting, there will be some additional steps to take, which I'll give an overview of, and some additional resources to get you over this first hurdle.</p> <p>If you are running "<a href="">A-PAtCHy</a>" web server (<a href="">possible name change coming?</a>) you will not be able to use <code>mod_php</code> for your PHP duties, as this method does not allow the web server to directly serve content of different virtual hosts using different versions of PHP. Instead, I recommend using PHP's "<a href="">FastCGI Process Manager</a>" service - aka FPM. This is a stand-alone service that Apache and NGINX will speak to using new-age technology from 1996 called <a href="">FastCGI</a>. It's still technically CGI, only, like, <em>Fast</em> (seriously, it works <em>really</em> well). Your web server hands off to this service with it's related FastCGI/proxy module.</p> <p>The process is quite similar for both web servers, and an article <a href="">over at Linode</a> covers the basics of this method for each<em>, but wait!</em> Finish reading at least this paragraph before you jump over there for both a caveat emptor and then some Debian specific derivations, if you need those (the article is Ubuntu-specific). In the article, they utilize the excellent PHP resources offered by <a href="">Ondřej Surý</a>. From this PPA/Debian APT resource, you can run concurrent installations of any of the following PHP versions (listed as of this writing): 5.6, 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, and 8.2. Do keep in mind, however, that versions prior to 8.0 are <a href="">[per]</a> now past their supported lifetime and no longer actively developed (see also <a href="">this FAQ</a> for more details). Debian-specific instructions for setting up this repository in apt (as opposed to Ubuntu PPA support) are also found within <a href="">Ondřej's instructions</a>. The remainder of the Linode process should still apply with that one change. OK, run along and get the PHP basics talking to your web server. If you'll be running Drupal (why wouldn't you?) then you'll want to ensure you have the version-specific php modules that <a href="">it requires</a> (this is for Drupal 9+, but links to earlier revisions also).  I'll wait...</p> <p>Assuming you have now progressed as far as having both versions of PHP you need installed, and followed the article from Linode above or whatever your favorite substitute source was, you likely noticed the special sauce that connects the web server to a particular PHP FPM service. In Apache, we have: <code>SetHandler "proxy:unix:/var/run/php/php8.0-fpm.sock|fcgi://localhost"</code>, and in NGINX flavor, it's: <code>fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;</code> These directives point to the Unix socket as defined for in the default PHP "pool" for the particular version. For reference, these are defined in <code>/etc/php/{version}/fpm/pool.d/www.conf</code> and therein will look like: <code>listen = /var/run/php/php8.1-fpm.sock</code>. So - all that's necessary to select your PHP version for your web server is to point to whichever socket location for the version of PHP you want.</p> <p>The Linode article does not go into handling multiple host names, and I won't go too deep here either as I've already navigated headlong into a bit of a scope-creep iceberg. The quick-and-dirty: for Apache, add another site configuration (as in, add another your_site.conf in /etc/apache/sites-available, and link to it from sites-enabled) repeating the entire <code>VirtualHost</code> and everything inside it, however, use a different listen port, or the same port, and add the <code>ServerName</code> directive to specify the unique DNS name. Likewise, with NGINX, except you here you repeat the full <code>server</code> block in another configuration, and changing the <code>listen</code> and/or <code>server_name</code> bits. Oh yeah - you'll probably be changing the folder location of the Drupal installation in there too, that should definitely help reduce some confusion.</p> <p>Phew - we should have the web server out of the way now!</p> <h2>Part 2: BASH-ing the `php` command</h2> <p>Next up: PHP on your command line. Here, I'm referring to what you get when you type <code>php</code> on your command line once logged in (via ssh) as the user that manages the web site. In this section, I'm assuming that per best-practices, there are different users for each site. This method does not help much if you have but one user...though I guess it can help if they're both using the same non-default version of php, in contrast to, say, other users on the server.</p> <h3>Which PHP is which?</h3> <p>When a server has multiple versions of PHP, only one of them at a given time will ever live at the path <code>/usr/bin/php</code>. Ordinarily, this is what you get when you type just <code>php</code> at the command line. This also is what you'll get whenever you run a file with a <a href="">shebang</a> of <code>#!/usr/bin/env php</code>, meaning if you run drush (or wp-cli, for our Wordpress friends), you'll get whatever PHP is found there as well. You can run <code>which php</code> if you'd like to see where php is found.</p> <p>At this point, definitely check <code>php --version</code>. If you are getting the version you want, you're are done with this article! Well, maybe not - you just want to switch to the account where you do require a different version of PHP than this gives you.</p> <p>So, <code>php --version</code> gives one version, but there should be multiple PHP executables on the system now, right? (You should have installed multiple, or else have multiple versions available at this point). So where are those? These can be directly executed by running them using their versions in their name. For example <code>php8.1</code> or <code>php7.4</code>. So, what is happening here that we just get "one proper php"? Well, a couple things.</p> <h3>Environmental conditions...</h3> <p>What, the smog? No...well, yes, that's a problem, but <a href="">some good things happened in 2022</a>. In this instance, our first issue comes from an environment variable. In particular: the venerable <code>PATH</code>, which contains a list of locations that the shell will use to look for a given executable as you enter a command name (or, again, as specified by a shebang). The PATH variable is a colon-delimited list, typically looking about like this: <code>/usr/local/bin:/usr/bin:/bin:</code>. The shell simply looks for the first occurrence of your command as it peeks in each directory in sequence (left-to-right). Is there a <code>/usr/local/bin/php</code>? That's what you'll get. If not, how about <code>/usr/bin/php</code>? And so on, until it finds one, or else you've mistyped <code>pph</code> and end up with <code>command not found</code> instead. You can see your path with <code>echo $PATH</code> (or try <code>which agaric</code>. I'm guessing you won't have an <code>agaric</code> program, this will tell you where it looked for it when it fail to find one).</p> <h3>What's the 'alternative': links all the way down</h3> <p>The second part of this equation is what is going on with the <code>/usr/bin/php</code> that was found. This alleged "The PHP" is actually a soft link to the current system-level default version of PHP that's installed. You can see how this situation is resolved with a command such as <code>readlink -f /usr/bin/php</code>. This command basically says "read the symbolic link (recursively, due to the <code>-f</code>, try it again without the <code>-f</code>!) and show what it's [ultimately] pointing to". This link (and those it links to) come from an "alternatives" system used by Debian-like systems that connects such things as the canonical name of an executable to a specific installed version. You can learn more about how how this is set up (for PHP, anyway) guessed it: <a href="">Ondřej's FAQ</a>.</p> <h3>So...the fix then?</h3> <p>Now, where we have multiple versions of PHP installed, it's generally impractical to change all the shebang lines to something else, but that is technically one way to do things. We're also assuming you want to use multiple versions simultaneously here - so updating via the alternatives system isn't a great option either - if you can even do that. There is a simple method to make this work, even as an unprivileged user: make your own link called <code>php</code> and make sure the shell can find it in the PATH.</p> <p>What we will do, is create our own <code>~/bin</code> folder, and make our link there. Then, we just make sure that <code>~/bin</code> is in our path, and comes <em>before</em> other locations that have a <code>php</code> file. There's no shortage of places for customizing the PATH (and bash, generally), and quite frankly, since I'm not positive what the canonical location is, I'll happily follow the <a href="">Debian manual</a> which says <code>~/.bashrc</code>. The particular file you'll want to use can be influenced by the type of shell you request (a login vs non-login, and interactive vs non-interactive). In the manual, part of their example looks like this:</p> <pre> <code> # set PATH so it includes user's private bin if it exists if [ -d ~/bin ] ; then PATH="~/bin${PATH+:$PATH}" fi export PATH </code></pre><blockquote> <p>Curious what that odd-looking <code>${PATH+:$PATH}</code> syntax is about? Most people just refer to PATH as <code>$PATH</code> when they want it, like this <code>PATH=~/bin:$PATH</code>, right? Well, yes, and that will probably work just fine, but that weird reference does have some smarts. These are something called parameter expansions. Note that in proper bash parlance, the thing we've been calling a variable this whole time is referred to as a parameter. Go figure...that certainly didn't help me find this reference documentation. If you are interested in shell programming (which I clearly think everyone is, or should be) these can be very helpful to know. You'll bump into these when performing various checks and substitutions on varia-er, parameters. Check out the <a href="">bash documentation</a> to figure this one out.</p> </blockquote> <p>OK, this looks good! Let's go ahead and add that to the <code>~/.bashrc</code> file in your ssh user's home folder. Now, you either need to source the updated file (with <code>. ~/.bashrc</code>), or just reconnect. Sourcing (abbreviated with that previous <code>.</code>, otherwise spelled out as <code>source ~/.bashrc</code>) essentially executes the referenced file in a way that can it can modify the current shell's context. If you were to just run <code>~/.bashrc</code> without sourcing it, it happily sets up an environment, but all that gets wiped out when the script ends.</p> <p>Now, let's get moving with this plan again. Make a bin folder in our ssh user's home folder: <code>mkdir ~/bin</code>. Finally, link the php version you want in there. Here, I'll do php8.1: <code>ln -s /usr/bin/php8.1 ~/bin/php</code>. Voila! Now when you run <code>php --version</code>, you'll get PHP 8.1!</p> <h4>Another diversion, really?</h4> <p>For the impatient: reconnect and skip to the paragraph. For the curious (or when you have time to come back): it turns out our shell has a bit of memory we never expected it to. If you've typed <code>php</code> sometime earlier in your session, and bash last found that it was the one in /usr/bin/php that came up first, it's now remembered that so it doesn't have to find it again. While you <em>can</em> just re-login - again! - you might also wan to take the reins of your system and try typing <code>hash -d php</code> (see <code>help hash</code> to learn what that does - the help command covers shell built-in functionality, like hash). At last, <code>php</code> really works the way we wanted! No more odd little shell corners hiding dusty references on us.</p> <h3>OMG, that works?</h3> <p>Finally...despite my droning on, we're making progress! At this point, when you call upon your <code>drush status</code>, it should actually run without errors (some things under php7.x just don't work now - as expected) and show that it's using the correct php version.</p> <h2>Part 3: Drush does speak SSH, but not YOUR SSH.</h2> <p>The Drush boffins graced us with doodads called site aliases that allow us to readily send commands to various sites we control. If you don't know about those, you'll have to <a href="">read up on them first</a>. The prolific <a href="">Moshe Weitzman</a> (along with some 310 contributors, and counting) didn't give us these because they were getting bored <a href="">after 20 years of Drupal</a>; They're pretty essential to working with Drupal effectively.</p> <p>Assuming you have a grasp of aliases under your belt, let's try a Drush command from our local environment to the remote system: <code>drush @test status</code>. OK - something is wrong here. My Drush just said I'm still using php7.4 and not my beloved 8.1...again! Does Drush have a memory too? No, it just doesn't care how you want your ssh sessions to work; those are left to you and Drush does it's own thing. The ssh connection that Drush opens has it's own context set-up, unlike the one we have when we've ssh'd in interactively. Thankfully, there's a quick fix for this - they key Drush needs to know is how to set the PATH up so it also gets our targeted PHP version. Let's add our modified PATH to the site aliases configuration, so Drush also knows what the cool kids are doing.</p> <p><code>live:<br />   host:<br />   paths:<br />     drush-script: /home/example-live/vendor/bin/drush<br />   env-vars:<br />     PATH: /home/example-live/bin:/usr/bin:/bin<br />   root: /home/example-live/web<br />   uri: ''<br />   user: example-live</code></p> <p>Note the specified PATH of <code>/home/example-live/bin:/bin:/usr/bin</code> places the bin directory in home at the beginning again. At last, when we run <code>drush @test status</code>, it's telling us the PHP it uses is the one that we use. We can share, Drush. We made it!</p> <h2>Thanks for stopping by!</h2> <p>That wraps it up for this one. Hopefully you now feel a little more confident you have some tools helping you master your environment. The version of PHP you need is now at your command. Now you can go get busy with your composer updates and other TODO's. By the way, if all this configuration headache is just not for you - check out our hosted <a href="">Drutopia</a> platform. We run an instance of this distribution, and provide all these niceties as part of our hosting setup, so <a href="">reach out if interested</a>. Either way, thanks for coming by!</p> </div> , <div class="paragraph paragraph--type-update paragraph--view-mode-default is-desktop columns clearfix"> <div class="is-12-tablet is-2-desktop column"> <time datetime="2023-02-03T12:00:00Z">February 3, 2023</time> </div> <div class="column"> <h2>Oh yeah!</h2> <p>I'm back! Because I knew this blog post wasn't quite long enough already...certainly not because it occurred to me that I left off another alternative I've used in a pinch.</p> <h3>The one-off</h3> <p>If you want to run a command using a different version of php, rather than rely on the shebang, you can always just call the desired script as an argument to php itself. When you run, e.g. <code>drush status</code>, your shell sees the <code>#!</code> on the first line, and uses that as the <a href="">default interpreter for the script</a>. In essence, your command is translated into <code>/usr/bin/env php drush status</code>.</p> <p>Rather than accept this default, you can specify the specific version on your command line, such as <code>php8.1 drush status</code>. In this case the <code>#!</code> is just ignored. Unfortunately, this method can only get you so far - particularly with drush. Drush often figures out what your command requires and in turn will spawn other php scripts to do the dirty work. These spawned scripts are unaware of the interpreter that drush itself was started with.</p> </div> </div> 2023 February 03 Chris Thompson How to filter a view by content that references the current node in modern Drupal <section data-headerbg="transparent" class="hero is-berry hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2023-01/web.jpg?itok=gweCfeT4" width="520" height="650" alt="A spiderweb covered in dew drops." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">How to filter a view by content that references the current node in modern Drupal </h1> <h2 class="subtitle">References and contextual filters </h2> </div> </div> </section> , <div class="flow_middle"> <p>It is often enough throughout the journey of building websites that you will desire to create very specific lists of content. In my current endeavor, I am working in Drupal 9 with 2 content types:<span> </span><em>Office location</em><span> </span>and<span> </span><em>Event</em><span> </span>. Each<span> </span><em>Event</em><span> </span>occurs at an<span> </span><em>Office location</em>, which is indicated by an<span> </span><em>Office location</em><span> </span>entity reference field on the<span> </span><em>Event</em>. Note that any info in this blog will also be relevant to Drupal 8, Drupal 10, and beyond.</p> <blockquote><p><strong>Here is the user story that we will be working through:</strong><br /> As a site visitor, when I view an<span> </span><em>Office location</em><span> </span>node, I should see a list of<span> </span><em>Events</em><span> </span>that are occurring at the location represented by the current node, so that I have an easy way to learn about events at locations relevant to me.</p> </blockquote> <p>To keep this short and sweet, I am going to assume that you have already created a view. However, all views are not created equal, so if you are not seeing the options that you are looking for while editing your view, maybe try creating a new one. We will be working with a<span> </span><code>block</code><span> </span>display—other options are possible, but this is the standard "Drupal approach".</p> <p>Once you are editing a block display for your view, open the<span> </span><code>Advanced dropdown</code><span> </span>on the right side. Here is what you will see:</p> <p><img alt="The advanced configuration options for a Drupal view." data-entity-type="file" data-entity-uuid="1f3a0259-fc27-4ba7-843b-171dbb704ac6" src="/sites/default/files/inline-images/Screenshot_48.png" width="668" height="753" /></p> </div> , <div class="flow_middle"> <h2 id="steps">Steps</h2> <h3 id="1-add-a-relationship-to-content-referenced-by-your-entity-reference-field">1, Add a relationship to content referenced by your entity reference field</h3> <p>Go ahead and click the<span> </span><code>Add</code><span> </span>button next to<span> </span><code>Relationships</code>.</p> <p><img alt="The relationship selection screen." data-entity-type="file" data-entity-uuid="94d52086-2699-49ee-9d05-3bf36a3f198d" src="/sites/default/files/inline-images/Screenshot_54_0.png" width="1643" height="660" /></p> </div> , <div class="flow_middle"> <p>In my case I am looking for<span> </span><code>Content referenced from field_office_location</code>. This is the entity reference field on my<span> </span><code>Event</code><span> </span>content type, and is the field that my view will use for comparison while querying data.</p> <p><code>Click add and configure relationships</code><span> </span>and then<span> </span><code>Apply</code><span> </span>on the next screen.</p> <h3 id="2-filter-your-view-results-by-content-that-references-the-current-node-id">2. Filter your view results by content that references the current node id</h3> <p>Click the<span> </span><code>Add</code><span> </span>button next to<span> </span><code>Contextual Filters</code></p> <p><code><img alt="The contextual filter selection screen." data-entity-type="file" data-entity-uuid="4c438481-1f12-4295-9546-2506ef929d85" src="/sites/default/files/inline-images/Screenshot_50.png" width="1650" height="1109" /></code></p> </div> , <div class="flow_middle"> <p>Select<span> </span><code>ID</code><span> </span>and click<span> </span><code>Add and configure contextual filters</code>.</p> <p><img alt="The contextual filter configuration screen." data-entity-type="file" data-entity-uuid="d5359eb4-4d3b-4809-99e6-422b7a8c392f" src="/sites/default/files/inline-images/Screenshot_51.png" width="2469" height="1634" /></p> </div> , <div class="flow_middle"> <p>The red boxes above indicate what needs to be done. Be sure to select the relationship you added to the<span> </span><code>Relationship</code><span> </span>field. This will allow the contextual filter to use the value of the<span> </span><em>Event</em>'s entity reference field for comparison.</p> <p>Click<span> </span><code>Provide default value</code><span> </span>and select<span> </span><code>Content ID from URL</code><span> </span>in the dropdown that appears. Note that if your nodes have URL aliases that do not include the content ID, Drupal will still provide the content ID to your contextual filter. Now your view will know which content to display by comparing the current node ID with the the values in the entity reference field of each listing.</p> <p>Lastly, Click<span> </span><code>Apply</code><span> </span>and do not forget to save your view.</p> <h2 id="the-end">The End</h2> <p>Congratulations! Now we know how to filter our view by content that references the current node. You probably still need to place your block and configure which nodes it should or should not display on, but I think that is a "How to" for another day. Have fun Drupaling!</p> </div> 2023 January 26 Keegan Rankin The development of Therapy Fidelity <section data-headerbg="transparent" class="hero is-dark hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2023-01/onion.jpg?itok=q4YZnufF" width="640" height="427" alt="A red onion sitting on a plate." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">The development of Therapy Fidelity </h1> <h2 class="subtitle">In support of evidence-based therapy </h2> </div> </div> </section> , <div class="flow_middle"> <p>Therapy Fidelity is a mobile interface that displays 132 sequential steps of cognitive behavioral therapy (CBT) for treating posttraumatic stress disorder (PTSD) to improve the fidelity of following the treatment protocol.</p> <h2>The Challenge</h2> <p>Leaders in the field of psychotherapy have been encouraging psychotherapists to adopt evidence-based treatments such as CBT for 20 years but it has been largely unsuccessful. Therapists have been unwilling and unable to rigorously follow CBT protocols with fidelity. Therapists cannot memorize all 132 steps, and patients have very limited access to the steps. As a result, treatment of patients suffers.</p> <p>Child psychiatrist Michael Scheeringa, MD identified a solution for this problem—a mobile interface that displays a CBT treatment protocol and that can be viewed by therapists and patients simultaneously. Through the guidance of Dr. Scheeringa and in partnership with Tulane University, Agaric developed the Therapy Fidelity web app.</p> <h2>Our Approach</h2> <h3>Designed For Use During a Therapy Session</h3> <p>When a patient attends their therapy session, it is far more likely that they arrive with a mobile phone than with a laptop. The need for a seamless mobile experience was one major reason we decided to build Therapy Fidelity as a single page application using React.js. Single page applications are great for reducing the number of network calls made to the host server since they do not need to request markup templates every time an end-user clicks to a different part of the site.</p> <p>Consequently, most of the network calls made by the app are made to read or write data to one of several third party APIs. We worked to mitigate these, too, by using the React Query library, a powerful and configurable tool for fetching, caching, and updating data in React. The result is an app that clearly indicates when data is loading, allows the user to continue navigating the app while update requests are being processed, indicates the results of those requests once they resolve, and prevents redundant fetching of data that has already been cached—in other words, a fast, smooth, and intuitive end-user experience, even on a limited-bandwidth device. One additional benefit of the data caching—many third party APIs will bill per API call after a limited number of requests; without caching that data in the browser, these API services could get expensive fast.</p> <h3>Simple, Consistent, and Flexible Design Elements with Tailwind UI/CSS</h3> <p>Throughout the building process, we looked to Tailwind UI for example components. By doing so, we made sure that the app has a consistent look and feel and is responsive so that it looks good on devices of all screen sizes. As this app mostly functions as a utility dashboard, the pre-designed components sufficed for the apps aesthetic needs, meaning that Tailwind UI saved us a lot of time on design work. Often we had to make minor adjustments to the markup so that it would suit our specific needs, and very seldom did we need to add any custom classes to our CSS stylesheet, which incidentally results in removing the often expensive overhead of building and maintaining our CSS architecture. Again, this is great for an app with simple aesthetic needs but which also needs re-usable and flexible responsive components.</p> <h3>HIPAA Certified Storage with TrueVault</h3> <p>In order to facilitate a quickest time-to-market, we opted to utilize an existing HIPAA certified provider for the storage, retrieval, and access controls/monitoring of protected data. TrueVault provides a JavaScript library for communicating with their RESTful backend. Their backend includes provisions for dealing with all aspects of the data, such as authentication, authorization, logging, and so-on. We were able to use their Vault-based data model to separate patient records into individual silo's per-therapist, and use their group-based security to set nearly all relevant permissions for the app's use. TrueVault certainly comes with its limitations and challenges, but it sure would have taken quite a bit more time and resources to build and certify a custom storage system.</p> <h3>Automated Quality Assurance using Typescript and React Testing Library</h3> <p><span>Additionally, we needed to ensure that each client's data is stored and retrieved accurately. TypeScript, which provides a type system for JavaScript, helped to ensure that as our application passed data around, it was of the expected type. Using a typed language also reduces the possibility for software errors generally. Beyond using good tools to write the application, checks must be in place to ensure that as it evolves, things do not break. Here, we leveraged React Testing framework and other tools to walk through the functionality of the application and ensure that everything continued to function correctly, and even stored the correct data; </span><span>By</span><span> mock</span><span>ing</span><span> (simulat</span><span>ing</span><span>) the TrueVault service we could inspect what the application would be storing there directly.</span></p> <h3>The Delicate Balance of Computing Large Sets of Sensitive Data</h3> <p>While the majority of permissions available to a logged in client were sufficient, there were cases where privileged tasks needed to be performed. Therapy Fidelity's <em>People Like Me</em> system is a great example. <em>People Like Me</em> involves a multi-step form which upon completion shows how many people with similar conditions were able to significantly improve their condition following the successful completion of a 3 month adherence to therapy protocol:</p> <p>First, a patient selects for comparison the results of a single survey they have completed, consisting of numerical indicators  for several psychological conditions (anxiety, depression, PTSD, etc.)</p> <p>Second, they configure which parameters they would like to match on. For example, a patient could choose to only see how many people who had shared a similar pre-therapy anxiety score were able to improve their condition following therapy.</p> <p>Finally, they view the results.</p> <p>Now it must be acknowledged that this system is parsing through and accessing patient data in real time. Of course, it would be a breach of patient privacy law for the patient to have such access to all of this other patient data, so we made sure that all data was scrubbed before reaching storage to reduce any personally identifiable information, and that computations occurred securely on a server before serving back results to the client.</p> <p>Rather than utilize something like a full VPS server that performed these functions for us, we were able to leverage functions-as-a-service to provide the specific privileged function access, specifically AWS Lambda functions. This helped us keep the surface area of the application as low as possible for security purposes, while providing server-side style functionality. The language we chose for these was Go, for its performance, excellent built-in framework, and because we were able to build for our needs with very minimal additional libraries, which again helps mitigate security concerns.</p> <p>While these functions-as-a-service were used several times throughout the site, it will be hard to forget Dr. Scheeringa's excitement about the realization of the <em>People Like Me</em> tool.</p> <h2>What's Next?</h2> <p>Therapy Fidelity was an excellent development experience—both for the thrill of developing a single page app and for carrying on Agaric's history of building <a href="">tools for medical and scientific communities</a>. As always, we are looking forward to our next opportunity to engage in these communities and build something wonderful.</p> <p>For any clinicians who happen to be reading this and are interested in using the Therapy Fidelity app for their own practice, <a href="">Scheeringa Mind</a> offers it as a service and provides a 30-day free trial. There are also plenty of <a href="">video tutorials</a> on how to use the app, which might help to preview just what you might be getting into.</p> <p>As Scheeringa Mind Company grows and Therapy Fidelity sees more use, we will be sure to publish any updates or major achievements. We are anticipating great strides in research on CBT protocol and look forward to seeing the results.</p> </div> 2023 January 24 Keegan Rankin, Chris Thompson Recommendations for social media as Twitter melts down: Host your own community <section data-headerbg="transparent" class="hero is-blue hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2022-11/Tmp_32458-El_Pavo_Real_en_la_Vyshyvanka_protege_al_hombre22109534.jpg?itok=tHZyyuHX" width="512" height="607" alt="Painting of a fancy peacock." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Recommendations for social media as Twitter melts down </h1> <h2 class="subtitle">Host your own community! </h2> </div> </div> </section> , <div class="flow_middle"> <p>As Elon Musk destroys Twitter, a lot of clients have asked about alternative social media, especially 'Mastodon'— meaning the federated network that includes thousands of servers, running that software <em><strong>and</strong></em> many other <abbr title="Free/Libre Open Source Software">FLOSS</abbr> applications, all providing interconnecting hubs for distributed social media. Agaric has some experience in those parts, so we are sharing our thoughts on the opportunity in this crisis.</p> <p>In short: For not-for-profit organizations and news outlets especially, this is a chance to host your own communities by providing people a natural home on the federated social web.</p> <p>Every not-for-profit organization lives or dies, ultimately, based on its relationship with its supporters. Every news organization, it's readers and viewers.</p> <p>For years now, a significant portion of the (potential) audience relationship of most organizations has been mediated by a handful of giant corporations through Google search, Facebook and Twitter social media.</p> <p>A federated approach based on a protocol called <a href="">ActivityPub</a> has proven durable and viable over the past five years. Federated means different servers run by different people or organizations can host people's accounts, and people can see, reply to, and boost the posts of people on the other servers. The most widely known software doing this is <a href="">Mastodon</a> but it is far from alone. <a href="">Akkoma</a>, <a href="">Pleroma</a>, <a href="">Friendica</a>, <a href="">Pixelfed</a> (image-focused), <a href="">PeerTube</a> (video-focused), <a href="">Mobilizon</a> (event-focused), and more all implement the ActivityPub protocol. You can be viewing and interacting with someone using different software and not know it— similar to how you can call someone on the phone and not know their cellular network nor their phone model.</p> <p>The goal of building a social media following of people interested in (and ideally actively supporting) your organization might be best met by setting up your own social media.</p> <p>This is very doable with the 'fediverse' and Mastodon in particular. In particular, because the number of people on this ActivityPub-based federated social web has already grown by a couple million in the past few weeks— and that's with Twitter not yet having serious technical problems that are sure to come with most of its staff laid off. With the likely implosion of Twitter, giving people a home that makes sense for them is a huge service in helping people get started— the hardest part is choosing a site!</p> <p>People fleeing Twitter as it breaks down socially and technically would benefit from your help in getting on this federated social network. So would people who have never joined, or long since left, Twitter or other social media, but are willing to join a network that is less toxic and is not engineered to be addictive and harmful.</p> <p>Your organization would benefit by having a relationship with readers that is not mediated by proprietary algorithms nor for-profit monopolies. It makes your access on this social network more like e-mail lists— it is harder for another entity to come in between you and your audience and take access away.</p> <p>But the mutual benefits for the organization and its audience go beyond all of this.</p> <p>When people discuss among one another what the organization has done and published, a little bit of genuine community forms.</p> <p>Starting a Mastodon server could be the start of your organization seeing itself as not only doing good works or publishing media, but building a better place for people to connect and create content online.</p> <p>The safety and stability of hosting a home on this federated social network gives people a place to build community.</p> <p>But organizations have been slow to adopt, even now with the Twitter meltdown. This opens up tho opportunity for extra attention and acquiring new followers.</p> <p>Hosting the server could cost between $50 to $450 a month, but this is definitely an opportunity to provide a pure community benefit (it is an ad-free culture) and seek donations, grants, or memberships.</p> <p>The true cost is in moderation time; if volunteers can start to fill that you are in good shape. A comprehensive writeup on everything to consider is here courtesy the <a href="">cooperatively-managed Mastodon server that Agaric Technology Collective chose</a> to join at <a href="">'s how to make the fediverse your own</a>.</p> <p>You would be about the first for not-for-profit or news organizations.</p> <p>You would be:</p> <ul><li>giving people a social media home right when they need it</li> <li>literally owning the platform much of your community is on</li> </ul><p>And it all works because of the federation aspect— your organization does not have to provide a Twitter, TikTok, or Facebook replacement <em>yourselves</em>, you instead join the leading contender for all that.</p> <p>By being bold and early, you will also get media attention and perhaps donations and grants.</p> <p>The real question is if it would divert scarce resources from your core work, or if the community-managing aspects of this could bring new volunteer (or better, paid) talent to handle this.</p> <p>Even one person willing to take on the moderator role for a half-hour a day to start should be enough to remove any person who harasses people on other servers or otherwise posts racist, transphobic, or other hateful remarks.</p> <p>Above all, your organization would be furthering your purpose, through means other than its core activities or publishing, to inform and educate and give people more capacity to build with you.</p> <p>Not surprisingly, <a href="">Drupal has already figured this out</a>!</p> </div> , <div class="flow_middle"> </div> , <div class="flow_middle"> <p> <small>Image credit: <a href="">Karlheinrichpasch</a>, <a href="">CC BY-SA 4.0</a>, via Wikimedia Commons.</small></p> </div> 2022 November 18 Benjamin Melançon How to add an audio player to a content node in Drupal 9 <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2022-11/tape-on-grass-crop.jpg?itok=un3pNFOz" width="473" height="427" alt="A white cassette tape laying in the grass." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">How to add an audio player to a content node in Drupal 9 </h1> </div> </div> </section> , <div class="flow_middle"> <p>Getting an audio player to show on your content nodes only takes a few steps. However, like many things in Drupal, knowing what those few steps are can be a bit of a challenge. This short guide will provide a basic introduction for anyone looking to use this core functionality on their site.</p> <h2>Step 1: Enable the Media module</h2> <p>The media module is available in Drupal core, and just needs to be enabled.</p> <h2>Step 2: Create a new <em>Media type</em></h2> <p>In the admin toolbar, navigate to <em>Structure &gt; Media types &gt; Add media type</em><br /> You may or may not have specific needs that determine how you name your media type. I simply named mine <em>Audio file</em>, and then selected <em>Audio file</em> for the <em>Media source</em> selection field. Don't forget to click save.</p> <h2>Step 3: Create an entity reference field on the entity type that should contain your audio player</h2> <p>In the most basic case, you will add a field to the content type where you want an audio player. When you get to the field type drop-down, select<em> Media</em> (under <em>Reference</em>). Of course, label your field according to your needs.</p> <p><strong>note:</strong> If you are using the paragraphs module, you might choose to create a new paragraph type and add a field there instead.</p> <p>Click <em>Save and continue</em></p> <p>Click <em>Save field settings</em></p> <p>Now you will be directed to the <em>Edit</em> tab for your new field. Select your desired settings. The important bit here is that you scroll to the <em>REFERENCE TYPE</em> field set, and click the checkbox next to your new media type (<em>Audio file</em> in my case).</p> <p>Scroll to the bottom and click <em>Save settings</em></p> <p>If you added this field directly to your content type, then you should be all set.<br /> If you are using paragraphs, don't forget to enable your new paragraph type on the paragraph field of your content type.</p> </div> 2022 September 20 Keegan Rankin Drupal 9 Component Plugin ContextException "not a valid context" after CTools, Symfony update <section data-headerbg="transparent" class="hero hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2022-07/old-fireworks.jpg?itok=rWsDoayW" width="440" height="500" alt="Old-fashioned half-used colorful red and blue fireworks box, labeled &#039;Thunder Bomb&#039; with the warning Caution-Explosive in large capitalized letters." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Drupal 9 Component Plugin ContextException "not a valid context" </h1> <h2 class="subtitle">after CTools, Symfony update </h2> </div> </div> </section> , <div class="flow_middle"> <p>Upgrading to CTools 3.10.0 and/or various Symfony upgrades can break the simple task of adding content on your Drupal site.</p> <blockquote><p>Drupal\Component\Plugin\Exception\ContextException: The context is not a valid context. in Drupal\Core\Executable\ExecutablePluginBase-&gt;getContextDefinition() (line 184 …</p> </blockquote> <p><strong><em>Update:</em></strong>  Seems confirmed this is CTools, <a href="">see critical bug issue</a>.</p> <p>And this fixed it:</p> <pre> git revert 0fbbbaf [main 7be1cd2] Revert fatal error standard code update "Update composer lock" 1 file changed, 163 insertions(+), 220 deletions(-) saharareporters [main]$ dco install # that is, composer install - dco is my alias for ddev composer Gathering patches for root package. Installing dependencies from lock file (including require-dev) Verifying lock file contents can be installed on current platform. Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update &lt;package name&gt;`. Package operations: 0 installs, 27 updates, 1 removal - Removing drupal/entityqueue (1.2.0) Deleting web/modules/contrib/entityqueue - deleted Gathering patches for root package. Gathering patches for dependencies. This might take a minute. - Downgrading drupal/core-composer-scaffold (9.4.4 =&gt; 9.4.3): Extracting archive - Downgrading symfony/yaml (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading symfony/translation (v4.4.44 =&gt; v4.4.41): Extracting archive - Downgrading symfony/event-dispatcher (v4.4.44 =&gt; v4.4.42): Extracting archive - Downgrading symfony/dependency-injection (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading symfony/console (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading symfony/filesystem (v5.4.11 =&gt; v5.4.9): Extracting archive - Downgrading symfony/config (v4.4.44 =&gt; v4.4.42): Extracting archive - Downgrading symfony/css-selector (v5.4.11 =&gt; v5.4.3): Extracting archive - Downgrading symfony/dom-crawler (v4.4.44 =&gt; v4.4.42): Extracting archive - Downgrading symfony/browser-kit (v4.4.44 =&gt; v4.4.37): Extracting archive - Downgrading symfony/finder (v6.1.3 =&gt; v6.1.0): Extracting archive - Downgrading symfony/validator (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading symfony/serializer (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading symfony/routing (v4.4.44 =&gt; v4.4.41): Extracting archive - Downgrading symfony/mime (v5.4.11 =&gt; v5.4.10): Extracting archive - Downgrading symfony/http-foundation (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading symfony/process (v4.4.44 =&gt; v4.4.41): Extracting archive - Downgrading symfony/var-dumper (v5.4.11 =&gt; v5.4.9): Extracting archive - Downgrading symfony/debug (v4.4.44 =&gt; v4.4.41): Extracting archive - Downgrading symfony/error-handler (v4.4.44 =&gt; v4.4.41): Extracting archive - Downgrading symfony/http-kernel (v4.4.44 =&gt; v4.4.43): Extracting archive - Downgrading drupal/migmag (1.8.1 =&gt; 1.8.0): Extracting archive - Downgrading symfony/string (v6.1.3 =&gt; v6.1.2): Extracting archive - Downgrading drupal/noreferrer (1.14.0 =&gt; 1.13.0): Extracting archive - Downgrading drupal/ctools (3.10.0 =&gt; 3.9.0): Extracting archive - Downgrading symfony/mailer (v5.4.11 =&gt; v5.4.10): Extracting archive</pre><p>And a drush cache-reload, of course.</p> <p>Full error, for search engines:</p> <p>The website encountered an unexpected error. Please try again later.</p> <p><em>Drupal\Component\Plugin\Exception\ContextException</em>: The context is not a valid context. in <em>Drupal\Core\Executable\ExecutablePluginBase-&gt;getContextDefinition()</em> (line <em>184</em> of <em>core/lib/Drupal/Core/Plugin/ContextAwarePluginTrait.php</em>).</p> <pre> Drupal\Core\Executable\ExecutablePluginBase-&gt;getContext(NULL) (Line: 116) Drupal\Core\Executable\ExecutablePluginBase-&gt;getContextValue(NULL) (Line: 91) Drupal\Core\Entity\Plugin\Condition\EntityBundle-&gt;evaluate() (Line: 77) Drupal\Core\Condition\ConditionManager-&gt;execute(Object) (Line: 84) Drupal\Core\Condition\ConditionPluginBase-&gt;execute() (Line: 376) Drupal\pathauto\Entity\PathautoPattern-&gt;applies(Object) (Line: 320) Drupal\pathauto\PathautoGenerator-&gt;getPatternByEntity(Object) (Line: 41) Drupal\pathauto\PathautoWidget-&gt;formElement(Object, 0, Array, Array, Object) (Line: 353) Drupal\Core\Field\WidgetBase-&gt;formSingleElement(Object, 0, Array, Array, Object) (Line: 220) Drupal\Core\Field\WidgetBase-&gt;formMultipleElements(Object, Array, Object) (Line: 111) Drupal\Core\Field\WidgetBase-&gt;form(Object, Array, Object) (Line: 181) Drupal\Core\Entity\Entity\EntityFormDisplay-&gt;buildForm(Object, Array, Object) (Line: 121) Drupal\Core\Entity\ContentEntityForm-&gt;form(Array, Object) (Line: 127) Drupal\node\NodeForm-&gt;form(Array, Object) (Line: 106) Drupal\Core\Entity\EntityForm-&gt;buildForm(Array, Object) call_user_func_array(Array, Array) (Line: 531) Drupal\Core\Form\FormBuilder-&gt;retrieveForm('node_article_form', Object) (Line: 278) Drupal\Core\Form\FormBuilder-&gt;buildForm(Object, Object) (Line: 97) Drupal\autosave_form\Form\AutosaveFormBuilder-&gt;buildForm(Object, Object) (Line: 73) Drupal\Core\Controller\FormController-&gt;getContentResult(Object, Object) call_user_func_array(Array, Array) (Line: 123) Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber-&gt;Drupal\Core\EventSubscriber\{closure}() (Line: 564) Drupal\Core\Render\Renderer-&gt;executeInRenderContext(Object, Object) (Line: 124) Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber-&gt;wrapControllerExecutionInRenderContext(Array, Array) (Line: 97) Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber-&gt;Drupal\Core\EventSubscriber\{closure}() (Line: 158) Symfony\Component\HttpKernel\HttpKernel-&gt;handleRaw(Object, 1) (Line: 80) Symfony\Component\HttpKernel\HttpKernel-&gt;handle(Object, 1, 1) (Line: 58) Drupal\Core\StackMiddleware\Session-&gt;handle(Object, 1, 1) (Line: 48) Drupal\Core\StackMiddleware\KernelPreHandle-&gt;handle(Object, 1, 1) (Line: 106) Drupal\page_cache\StackMiddleware\PageCache-&gt;pass(Object, 1, 1) (Line: 85) Drupal\page_cache\StackMiddleware\PageCache-&gt;handle(Object, 1, 1) (Line: 48) Drupal\Core\StackMiddleware\ReverseProxyMiddleware-&gt;handle(Object, 1, 1) (Line: 51) Drupal\Core\StackMiddleware\NegotiationMiddleware-&gt;handle(Object, 1, 1) (Line: 23) Stack\StackedHttpKernel-&gt;handle(Object, 1, 1) (Line: 709) Drupal\Core\DrupalKernel-&gt;handle(Object) (Line: 19) </pre> </div> 2022 July 29 Benjamin Melançon Drupal 9.4 installation with existing configuration fails because "unable to uninstall the MySQL module"!? <div class="flow_middle"> <p>Here is how to deal with the surprising-to-impossible-seeming error "Unable to uninstall the <em class="placeholder">MySQL</em> module because: The module 'MySQL' is providing the database driver 'mysql'.."</p> <p>Like, why is it trying to uninstall anything when you are installing? Well, it is because you are installing <em>with existing configuration</em>— and your configuration is out-of-date. This same problem will happen on configuration import on a Drupal website, too. (See update below for those steps!)</p> <p>Really this error message is a strong reminder to <em>always</em> run database updates and then commit any resulting configuration changes after updating Drupal core or module code.</p> <p>And so the solution is to roll back the code to Drupal 9.3, do your installation from configuration, and then run the database updates, export configuration, and commit the result.</p> <p>For example:</p> <pre><code class="language-bash">git checkout &lt;commit-hash-of-earlier-composer-lock&gt; composer install drush -y site:install drutopia --existing-config git checkout main composer install drush -y updb drush -y cex git status # Review what is here; git add -p can also help git add config/ git commit -m "Apply configuration updates from Drupal 9.4 upgrade" </code></pre> <p>The system update <code>enable_provider_database_driver</code> is the <code>post-update</code> hook that is doing the work here to "Enable the modules that are providing the listed database drivers." Pretty cool <a href="">feature</a> and a strong reminder to always, always run database updates and commit any configuration changes immediately after any code updates!</p> <h3>Update: Steps on for resolving the unable to uninstall MySQL module error on configuration import</h3> <p>This is what you probably already did, before the <code>drush -y cim</code> failed (luckily, luckily it failed).</p> <pre><code class="language-bash">composer update drush -y updb </code></pre> <p>All that is great! Now continue, <em>not</em> with a config import, but with a config export:</p> <pre><code class="language-bash">drush -y cex git status # Review what is here; git add -p can also help git add config/ git commit -m "Apply configuration updates from Drupal 9.4 upgrade" </code></pre> <p>Remember, after every composer update and database update, you need to do a configuration export and commit the results— database updates can change configuration, and if you do not commit those, you will undo these intentional and potentially important changes on a configuration import. If you ran into this problem on a configuration import, it is a sign of breakdown in discipline in following these steps!</p> <p><strong>Every</strong> time you bring in code changes with composer update all this must be part and parcel:</p> <pre><code class="language-bash">composer update drush -y updb drush -y cex git status # Review what is here; git add -p can also help git add config/ git commit -m "Apply configuration from database updates" </code></pre> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2022-06/firefox-cannot-connect-dinorabbit_0.png?itok=lbGvSRaF" width="525" height="432" alt="A cartoon dinosaur trying to plug two cords together but their arms are too short." typeof="foaf:Image" /> </div> 2022 June 17 Benjamin Melançon Uniting Visions: Kicking off Thursday 3pm ET planning & building sessions for democratic conversation scaling platform, Visions Unite <section data-headerbg="transparent" class="hero hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2022-06/stand-deliver-dueling-free-speech.jpg?itok=R3X3mqEi" width="500" height="413" alt="Silhouettes of a people on soapboxes shouting through bullhorns at a crowd, demonstrating stand / deliver, a portable dueling free speech unit, a mobile box equipped with two platforms and megaphones." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Uniting Visions </h1> <h2 class="subtitle">Kicking off Thursday 3pm ET planning &amp; building sessions for democratic conversation scaling platform, Visions Unite </h2> </div> </div> </section> , <div class="flow_middle"> <p>Hi friends and collaborators, <a href="">join us</a> today at 3pm ET (or any subsequent Thursday at 3) as we kick off a series of research, planning, discussion, and building sessions for Visions Unite.</p> <p>As our primary pro bono project, Agaric is working on Visions Unite, "where people seeking to make the world more whole can share ideas and information and gather the commitment and resources to build power to be the change we need", which a dozen projects have tried to do—what makes this different is sharing power via democratic mass communication.</p> <p>Here are some <a href="">initial user stories for Visions Unite</a>.</p> <p>Help plan and build the interface and underlying technology! (Drupal friends, we have been leaning against Drupal but might do it for the <abbr title="Minimum Viable Product">MVP</abbr>— would love to hear your thoughts for or against.)</p> <p>Connection info will always be up-to-date at <a href=""></a> (for these sessions we are taking over most of our Show &amp; Tell hour, which is weekly on Thursdays 3pm Eastern).</p> </div> 2022 June 16 Benjamin Melançon, Keegan Rankin Here is a social media microblog-sized post just to say we are syndicating to Mastodon and Twitter thanks to the Indieweb module! <div class="flow_middle"> <p>It's great to be here, and <a href="">there</a>, and <a href="">there</a>.</p> <p>Thanks <a href="">Indieweb module for Drupal</a>!</p> </div> 2022 March 08 Benjamin Melançon, Michele Metts Create and use a custom permission in your module <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2021-03/undraw_secure_login_pdn4.png?itok=9woH369n" width="650" height="448" alt="Laptop with lock icon." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Create and use a custom permission in your Drupal module </h1> <h2 class="subtitle">Define a permission in Drupal code and check access programmatically </h2> </div> </div> </section> , <div class="flow_middle"> <p>You can define your own permissions for the Drupal permissions page (<em>/admin/people/permissions</em> in modern Drupal, Drupal 8, 9, 10, and beyond) and then add conditional options to your code to do different things based on the role of the user and the permissions configured by a site administrator.</p> <p>Here's how.</p> <h3>Create a modulename.permissions.yml file</h3> <p>This simple file has the permission machine name (lower case with spaces) and a title (Sentence case) with an optional description.</p> <p>For our module, which has a particularly long name, that file is <code>drutopia_findit_site_management.permissions.yml</code> and its contents are like so:</p> <pre><code class="language-yaml">access meta tab: title: 'Access meta tab' description: 'Access meta information (author, creation date, boost information) in Meta vertical tab.' </code></pre> <p>You can repeat lines like these in the same file for as many permissions as you wish to define.</p> <h3>Check for that permission in your code</h3> <p>The process for checking permissions is simply to use a user object if that's handed into your code, or to <a href="">load the current user</a> if it's not, and use the <a href="">hasPermission()</a> method which returns TRUE if that user has permission and FALSE if not.</p> <p>For example, in a form alter in our <code>drutopia_findit_site_management.module</code> file:</p> <pre><code class="language-php">/** * Implements hook_form_BASE_FORM_ID_alter() for node_form. * * Completely hide the Meta vertical tab (field group) from people without permission. * */ function drutopia_findit_site_management_form_node_form_alter(&amp;$form, FormStateInterface $form_state, $form_id) { // If the current user has the permission, do not hide the Meta vertical tab. if (\Drupal::currentUser()-&gt;hasPermission('access meta tab')) { return; } // Code to hide the meta tab goes here, and is only reached if the user lacks the permission. // ... } </code></pre> <p>See all this code in context in the <a href="">Find It Site Management module</a>.</p> <p>To learn more about defining permissions in modern Drupal, including dynamic permissions, you can see the <a href="">change record for when the new approach replaced hook_permission()</a>.</p> </div> 2021 March 10 Benjamin Melançon Why we resist Zoom and choose BigBlueButton for video chat <div class="flow_middle"> <p><img alt="BigBlueButton screenshot of Agaric's Show and Tell" data-entity-type="file" data-entity-uuid="23ad61ea-a642-421c-91f8-b8ffaeb73f75" src="/sites/default/files/inline-images/bbbinsideroomS%2BTscreenshot.png" width="1322" height="643" /></p> <p>We can look at the recent popularity of some widely used platforms like Zoom and ask ourselves some questions as to why we still use them when we know a lot of terrible things about them. Agaric prefers to use a free/libre video chat software called BigBlueButton for many reasons, the first one being the licensing, but there are many reasons.</p> <p>Zoom has had some major technology failures, which the corporation is not liable to disclose. At one point, a vulnerability was discovered in the desktop Zoom client for MacOS that allowed hackers to start your webcam remotely and launch you into a meeting without your permission. The company posted a note saying that they fixed the issue. Unfortunately, the Zoom source code is proprietary and we are not even allowed to look at it. There is no way for the community to see how the code works or to verify that the fix was comprehensive.</p> <p>The Zoom Corporation stated early on that the software was encrypted end-to-end (E2EE) from your device to the recipient's device. This was untrue at the time, but the company states that it has been corrected for users on their client app. While it is no longer true that E2EE is unsupported, it does require that you use the proprietary Zoom client for E2EE to work. Without E2EE, any data that is retrieved on its way from your computer to a server can be accessed! The only real security is knowing the operators of your server. This is why Agaric uses trusted sources like <a href=""></a> for most of our projects and we have a relationship with our BigBlueButton host. The <a href="">Intercept also revealed</a> that Zoom users that dial in on their phone are NOT encrypted at all</p> <p>BigBlueButton does not have a client app and works in your browser, so there is no E2EE. The idea for E2EE is that with it,  you "do not have to trust the server operator and you can rely on E2EE" because the model implies that every client has keys that are protecting the transferred data. However: you MUST still use a proprietary client in order to get the benefits of E2EE support, so once again you MUST trust Zoom as you have no permission to examine the app to determine that the keys are not being shared with Zoom.</p> <p>Of course there is always the fact that hackers work day and night to corrupt E2EE and a Corporation is not obligated to tell you the customer every time there has been a security breach, and this information is usually buried in the terms of service they post - sometimes with a note saying the terms are subject to change and updates. A Corporation is not obligated to tell you, the customer when there has been a security breach" unless any personal information is exposed. There are now mandatory timely disclosure requirements for all states: <a href=""></a> ...Can Zoom really be trusted? As with some laws, the fine that is applied is low and affordable and subject to the interpretation of the courts and the status of knowledge your lawyer is privvy to - meaning most Corporations normally have a battery of lawyers to interpret the law and drag the case out until you are... broke.</p> <p>In the case of BigBlueButton encryption, E2EE would only make sense if there are separate clients using an API to connect to the BBB server so a user does not have to trust the BBB server operator. If the user trusts the server operator, then there would be no need for E2EE." Lesson learned: It is always best practice to know and trust your server hosts as they are the ones that have the keys to your kingdom. </p> <p>Some technology analysts consider <a href="">Zoom software to be malware</a>. Within companies that use Zoom, employers are even able to monitor whether or not you are <a href="">focusing on the computer screen during meetings</a> which seems excessively intrusive. Speaking of intrusive, the <a href="">Zoom Corporation also shares your data with FaceBook</a>, even if you do not have a FB account - that could be a whole blog in itself, but just being aware of some of the <a href="">vulnerabilities</a> is a good thing to pass on. Some of the <a href="">bad stuff remains</a> even if you uninstall the Zoom app from your device! Even though <a href="">a class action suit</a> was filed over privacy issues, the company stock still continued to rise.</p> <p>Those are many reasons why we do not support Zoom. But there are also many reasons why we prefer BBB over Zoom. Besides, BBB has many great features that Zoom lacks:</p> <p>1. Easily see who is speaking when their name appears above the presentation.<br /> 2. Chat messages will remain if you lose your connection or reload and rejoin the room.<br /> 3. Video is HD quality and you can easily focus on a persons webcam image.<br /> 4. Collaborative document writing on a shared Etherpad.<br /> 5. Easily share the presenter/admin role with others in the room.<br /> 6. Write closed captions in many languages, as well as change the language of the interface.<br /> 7. An interactive whiteboard for collaborative art with friends!</p> <p><img alt="Collaborative artwork on BBB whiteboard." data-entity-type="file" data-entity-uuid="b1d8e0eb-2946-4399-90bb-fa0ab9b9c2ea" src="/sites/default/files/inline-images/2021catBBBwhiteboard.png" width="702" height="381" /></p> <p>One huge advantage of free software, like BBB, is that you can usually find their issue queue where you can engage with the actual developers to report bugs and request feature enhancements. Here is a link to the <a href="">BigBlueButton issue queue</a>.</p> <p>So, why do people keep using a platform like Zoom, even though there are many features in BigBlueButton that are much better? </p> <p>There is very little publicity for free software and not many know it exists and that there are <a href="">alternative solutions</a>. You can find some great suggestions of software and switch to it by using this site called <a href=""></a>. The marketing budget for Zoom is large and leads you to believe it has everything you will need. Sadly their budget grows larger everyday with the money people pay for subscriptions to the platform. As a result, many people go with it as it is already used by their friends and colleagues, even though there are reports of irresponsible behavior by the Zoom Corporation. This is why the New York school system does not use Zoom and many organizations are following suit. The company gives people <a href="">a false sense of security</a> as it is widely used and very popular.</p> <p>Of course, there are reasons to <a href="">avoid other proprietary chat platforms</a> too...</p> <p>Agaric offers BigBlueButton for events and meetings. Check out our fun BBB website at <a href="">CommunityBridge</a> and test drive the video chat yourself!</p> <p>If this discussion interests you, please share your thoughts with us in the comments.</p> <p>Looking to learn more about problems with Zoom? There are a lot of articles about Zoom scandals.</p> <ul><li>If you need <a href="">to leak a zoom video without being identified</a>, you need to be very careful!</li> <li><a href="">Zoom censorship of Palestine seminars sparks fight over academic freedom</a></li> <li><a href="">Zoom encryption is not suited for secrets</a></li> <li><a href="">Zoom is unclear about whether or not the app is actually end-to-end encrypted</a>, which it isn't.</li> <li>And, yes - <a href="">the wikipedia page</a> has a good summary of some issues</li> <li>Stallman provides resources on <a href="">what is bad about Zoom</a> also</li> </ul><p>Looking to learn more about protecting your privacy online? These links have some helpful information and videos for tech-savvy people and organic folks alike!</p> <ul><li><a href="$/search?q=the%20hated%20one">The Hated One</a></li> <li><a href="">Rob Braxman Tech</a></li> <li><a href="">Safe Services</a></li> </ul><p class="is-6 subtitle">2021 could be the year we all begin to STOP supporting the Corporations that oppress us. </p> <p class="is-6 subtitle">Special thanks to Keegan Rankin for edits!</p> <p> </p> </div> 2021 January 23 Michele Metts, Chris Thompson Drupal toolbar not working in dev environments in Firefox? Here's why. <div class="flow_middle"> <p>Drupal's toolbar second level of menu options and dropdown not showing? Look for "Uncaught DOMException: The quota has been exceeded." errors, as viewable in the <a href="">Firefox web console</a>. If you see them, the problem is likely due to sites sharing a top-level domain—which is likely if you are using a local development environment like <a href="">DDEV</a>, and you working on way too many sites at once—combined with a pretty bad <a href="">Firefox bug that will be fixed in the next release</a>.</p> <p>To quote <a href="">Nathan Monfils</a>:</p> <blockquote> <ol><li>Everything from your public domain (abc.tld) counts against your quota, even if it is in a seemingly unrelated subdomain (e.g. and</li> <li>The quota is not recomputed properly, requiring a firefox restart after clearing your data on other subdomains</li> </ol></blockquote> <p>Note this may affect all sorts of applications, not just Drupal, when you have them running on multiple subdomains of the same top-level domain. So this isn't just about local development environments (and i dislike that DDEV shares their own top-level domain across all the instances you are working on, and while it <a href="">can be changed</a> i've accepted its way of doing things so i'm on the same page with other developers by default).</p> <p>Sure, closing more tabs and restarting Firefox could (predictably) have fixed this—and a lot else that's wrong with me, according to everyone i know—but why do that when i can open <em>more</em> tabs and learn precisely how broken everything around me really is?</p> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2021-01/toomanytabs_0.jpeg?itok=0ofeuqK8" width="1024" height="768" alt="A drawing of a laptop with open tabs extending outside of the laptop as horizontally tiled windows." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <p>I am very happy the bug is fixed and this blog post will be obsolete in mere days! Usually this sort of technical noodlings get relegated to our <a href="">raw notes, currently hosted through GitLab</a>, but figured at least a few other Drupal developers would want to know what has been going on with their toolbars.</p> <p>Image credit: "Too Many Tabs" by <a href="">John Markos O'Neill</a> is licensed with <a href="">CC BY-SA 2.0</a>.</p> </div> 2021 January 19 Benjamin Melançon;s why. Upgrade from Drupal 6 or 7 when it is right for you: In the era of modern Drupal, release cycles of major versions have only minor importance <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2021-01/undraw_update_uxn2.png?itok=SmY6kzuE" width="650" height="404" alt="&quot;&quot;" typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Upgrade from Drupal 6 or 7 when it is right for you </h1> <h2 class="subtitle">In the era of modern Drupal, release cycles of major versions have only minor importance </h2> </div> </div> </section> , <div class="flow_middle"> <p><abbr title="Too Long Didn't Read">TLDR</abbr>: Drupal 7 has a much longer lifespan than the (already pushed back) official date, and Drupal 8 has an essentially infinite lifespan because it updates in-place to Drupal 9 easily (and the same will be true of Drupal 10, 11, ∞.). There's no reason to rush an <a href="">upgrade</a>— but there's no reason to wait either.</p> <p>That's the short version.</p> <p>A client recently wrote to Agaric about Drupal 7 / Drupal 8 / Drupal 9 project planning questions:</p> <blockquote> <p>With the EOL for Drupal 7 in Nov of 2022, and the EOL for Drupal 8 in Nov 2021, is there a reason we should move a D7 site to D8 at all this year? Seems like we might want to move directly to D9? We don’t want to feel pushed up against a wall with a “new” site build in Drupal 8, if we can limp along in D7 for a couple more years while we develop a D9 site with a longer lifespan. I’m wondering if you might have time to discuss pros and cons briefly so we can get a good plan together for moving forward.</p> </blockquote> <p>I started typing and almost did not stop:</p> <ol><li> <p>No one believes me when i say this, but i repeat my assurance that Drupal 7 will be well-supported commercially until 2030 or later (Drupal 6, released in 2008, still has semi-official long term support until <a href="">at least February 24th, 2022</a>— and Drupal 7 has a larger install base than Drupal 6 ever did, and <a href="">currently has the largest install base of any version of Drupal by far</a>, with more than half a million tracked installs.</p> <p>Drupal 7 will be supported by the community for a long time. You do not have to feel <em>pushed</em> to a new version, like, ever.</p> </li> </ol> </div> , <div class="flow_middle"> <img loading="lazy" src="/sites/default/files/styles/max_1300x1300/public/2021-01/usage-statistics-for-Drupal-core.png?itok=OM7hdgVx" width="1300" height="706" alt="Stacked area chart showing Drupal 7 with more than half of all currently tracked Drupal core installs, which is more than half a million." typeof="foaf:Image" /> </div> , <div class="flow_middle"> <ol start="2"><li> <p>We do recommend moving directly to Drupal 9 (which was released on June 3rd of 2020), however:</p> </li> <li> <p>Moving to Drupal 8 or to Drupal 9 is much the same. Drupal 8 starts what i call the "modern Drupal" era. Whereas for going from Drupal 5 to 6 or 6 to 7 or 7 to 8 broke backward compatibility and might as well be a full rebuild (so we would often recommend hopping a version, say, stay on Drupal 6 and wait for Drupal 8 to be ready) going from Drupal 8 to 9 is closer to going from Drupal 8.8 to 8.9— an in-place upgrade from 8.9 to 9.0. Going from 9 to 10 will work the same, and that's the plan and promise for Drupal 8 on out.</p> </li> <li> <p>All that said, if anything significant needs fixing on your current Drupal 7 site, or you are looking to make any improvements, you'll want to do that on Drupal 8+ or Drupal 8/9 as we phrased it back when Drupal 9 was still a pretty recent release, but now we can just say Drupal 9— or, as i call it to emphasize the decreased importance of major version numbers, <strong>modern Drupal</strong>.</p> </li> </ol><p>Agaric is always happy to <a href="">discuss more</a>! Mostly what i'm saying here is the useful things to talk about are the specific goals for the sites—when you want to accomplish what—because the official support cycles are a distraction in the current context of Drupal. So make sure your current site is <a href="/maintenance-and-support">maintained</a>, but take your time to get clear on your objectives, and contact Agaric or the Drupal professionals of your choice when you think it might make sense to <a href="">upgrade your site into the era of modern Drupal</a>.</p> </div> 2021 January 13 Benjamin Melançon Community-managed categories <div class="flow_middle"> <p>Community-managed categories is an idea from just about the beginning of my time as a web developer.  As "Community-managed taxonomy" it was my submission to the 2007 Summer of Code, barely a couple years into my time as a Drupal developer.</p> <blockquote><p>The Drupal module Community Managed Taxonomy, or CMT, variously known as Community Managed Categories, seeks to bring the possibility of mass participation to categorization (taxonomy) and therefore potentially site structure.</p> </blockquote> <p>As the <a href="">project page</a> put it:</p> <blockquote><p>Community-managed taxonomy (CMT) opens categorization of content to the site's community. Users can influence both what terms nodes are tagged with and how these terms are themselves organized.</p> <p>It can also be used to make structured tags on the fly. Users do not need to be logged in to make or propose terms for content.</p> </blockquote> <p>I very much hope to get back to this work.  I never did get the module working—Agaric was a new company and my father became ill and was killed by the hospital that summer, and my mentor and i were not the best match—and after failing to complete the module and get the second stipend, the time and circumstance has not yet returned.  Please <a href="">contact us</a> if you may be able to help make the time right in 2021!</p> <p>This capability to allow a large community to coordinate in categorizing may be more salient when democratically-moderated mass communication is made possible.  That's a goal i have pursued even longer, and i think it is more important and possibly logically prior to community-managed taxonomy.  First we need community-managed communication, so that the resources we build together can reach, be distributed to, the people who should know.  But building community-managed communication may bring up the need for community-managed categories directly, too— how do we decide what the groups are that we can manage communication <em>within</em>?  That's a job for community-managed taxonomy, probably.</p> <p>It is all tied up to what I have been aiming for for decades.  This description is from 2008:</p> <blockquote><p>People have always needed something better than mailing lists— or other communication tools as they exist now. We need something that can reach millions of people (or billions– everyone) and still be open to everyone on an equal basis. Reaching everyone means filtering to reduce quantity and increase quality. Staying open to everyone means that the filtering must not be controlled by any group, must in some true sense belong to everyone.</p> <p>The Internet has this potential. (The issue of access remains crucial, but is separate from helping the Internet reach closer to its potential for people who do have access.)</p> <p>People Who Give a Damn is incorporated as a nonprofit organization to connect, without interference and without wasting anyone's time, everyone who gives a damn.</p> <p>As the first technique to achieve this, anyone signed up to receive messages in a network can submit a message to be sent. The message will be publicly available immediately, but it will be moderated by a random sample of other people in the network pulled to serve jury duty. If they decide it is important enough to send to everyone in the network, it is sent to everyone. If not, the message will have more limited distribution to the sender's personal contacts and possibly to groups within the overall network to which the sender belongs.</p> <p>This is simple. Yet it will make possible horizontal communication, not top-down few-to-many broadcasts, that is also mass communication. We need horizontal mass communication because we need mass cooperation and collaboration. It is possible with current technology, and necessary for the well-being of ourselves, our friends and family, our fellow humans, our Earth. Anyone interested in updates on progress or how they can help, please contact me.</p> </blockquote> </div> 2021 January 01 Benjamin Melançon Double-edged Raiser: Past time to ditch Blackbaud <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img loading="lazy" src="/sites/default/files/styles/max_650x650/public/title-image/2020-07/double-edged-razor.jpg?itok=pl8XyuaS" width="650" height="650" alt="A plain two-edged razor blade (for a pun on double-edged razor)." typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">Double-edged Raiser </h1> <h2 class="subtitle">Past time to ditch Blackbaud </h2> </div> </div> </section> , <div class="flow_middle"> <p>I have watched in sadness and sometimes anger as large non-profit after large non-profit collectively poured enough money into Raiser's Edge and other Blackbaud licenses and consulting services to fund many feature enhancements for the main <abbr title="Free/Libre Open Source Software">FLOSS</abbr> alternative, <a href="">CiviCRM</a>— improvements which would then be free for everyone, forever.</p> <p>I have never met anyone who actually likes Blackbaud products and services. However, many organizations felt they were the only safe option, in the sense of claiming to have everything an enterprise needs.</p> <p>Now, Blackbaud failed to secure its servers sufficiently and large amounts of its clients' donor data, including personally identifying information, was obtained in a ransomware attack. This was back in May. Blackbaud ultimately paid the ransomer to allegedly destroy the data they obtained— and only late in July finally told their customers what happened.</p> <p>As the American Civil Liberties Union wrote to all its supporters, current and past (including myself), this is a rotten situation:</p> <blockquote> <p>In all candor, we are frustrated with the lack of information we've received from Blackbaud about this incident thus far. The ACLU is doing everything in our power to ascertain the full nature of the breach, and we are actively investigating the nature of the data that was involved, details of the incident, and Blackbaud's remediation plans.</p> <p>We are also exploring all options to ensure this does not happen again, including revisiting our relationship with Blackbaud.</p> </blockquote> <p>Fortunately, none of Agaric's clients are affected. But we hope everyone using or considering using Blackbaud and other proprietary services for their most important data will look at free/libre open source solutions. Code you (or your technology partner) can see and contribute to means you truly can do anything. And if you put aside the money that would be gouged out of your organization by the <a href="">eTapestry, Kintera, and Convio-swallowing</a> monopolist Blackbaud, you probably can afford to.</p> <p>At <a href="">Agaric,</a> we have recently been working with CiviCRM more recently (building on experience dating back fifteen years!) and we know our friends at <a href="">Palante Technology Cooperative</a> and <a href="">myDropWizard</a> are well-versed in CiviCRM, as are <a href="">many others</a>. Please consider this when weighing your options for maintaining a strong, ethical relationship with your supporters, and <a href="">let us know</a> if you have any thoughts or questions!</p> </div> 2020 August 03 Benjamin Melançon Are you building communities of practice for community health in this time of public recognition of injustice? <div class="paragraph paragraph--type-update paragraph--view-mode-default is-desktop columns clearfix"> <div class="is-12-tablet is-2-desktop column"> <time datetime="2021-01-25T12:00:00Z">January 25, 2021</time> </div> <div class="column"> <h3>Blog Update: Keep this conversation alive!</h3> <p>In this post, we call out for "Birds of a feather" to join us at DrupalCon, which has come and gone. However, this conversation remains relevant to our political condition and relevant to our work! Our scientific and government entities must continue to increasingly acknowledge racism as a public health threat. We believe that harnessing the power of data within our own communities is a path to the change that we want to see. Please help us keep this post and the discussion it provokes alive and circulating!</p> </div> </div> , <div class="flow_middle"> <h3>Original Post</h3> <p>The struggle to take seriously the <a href="">impact of racism on public health during the covid-19 crisis</a> spotlights the importance of health communities of practice and their need to have high-quality, data-driven discussions.</p> <p>Environmental racism was <a href="">forced into the national conversation by Flint, Michigan</a> years ago and <a href="">COVID-19 has made long-standing problems of racism in healthcare provision</a> impossible to ignore.  The <a href="">rebellion against police repression</a>, which started in Minneapolis, is itself a reaction to the public health issues caused by policing. This reaction was predictable, and Minneapolis resident <span><span><a href="">D.A. Bullock</a>, indeed, <a href="">predicted it</a>.</span></span></p> <p>So, moving forward...</p> <p>How do we have data-informed conversations effectively within our communities?</p> <p>How do we expand them to include more health professionals and community members from outside of what is currently recognized as the healthcare industry?</p> <p>We need resolute answers to both of these questions.</p> <p>Thanks to <a href="">a client giving us a shout out</a>, Agaric has come to acknowledge <a href="">our experience building for health communities</a> and the role that we are able to take in this conversation. We would love to talk and learn with others more deeply about these issues.  We're hosting a "Birds of a Feather" (people interested in the same topic coming together) at DrupalCon Global today at 3:15 Eastern Time to talk about facilitating discussion among healthcare practitioners, researchers, and the public.</p> <p>Here are some more questions to get you thinking!</p> <p>What are the next steps for healthcare workers and researchers? What are the next steps for any person who cares about our communities? How do we move important conversations into the public realm sustainably? The stakes for both well-informed and broad-based discussion are clearer than ever.  We know <a href="">pressure, policy, and practice are what make change</a>; what is our role?</p> <p>Please leave your comments even if you can't join us today!</p> </div> 2020 July 16 Benjamin Melançon, Michele Metts Drupal migrations reference: List of configuration options for destination plugins <div class="flow_middle"> <p>In the previous article we provided a <a href="">reference of available configuration options for migrate source plugins</a>. In today’s article we are doing something similar for destination plugins. We will present a reference of available configuration options for migrate destination plugins provided by Drupal core and some contributed modules. Knowing which options are available might require some Drupal development knowledge. By providing this reference it should make the process of writing migrations easier.</p> <p><img alt="List of configuration options for destination plugins" data-entity-type="file" data-entity-uuid="08d46f6a-80ce-4aad-a850-11fb3fa199c9" src="/sites/default/files/inline-images/migrations-destination-plugin-configuration-options-list.jpg" width="960" height="540" /></p> <p>For each migrate destination plugin we will present: the module that provides it, the class that defines it, the class that the plugin extends, and any inherited options from the class hierarchy. For each plugin configuration option we will list its name, type, a description, and a note if it is optional.</p> <h2 id="DestinationBase">DestinationBase (abstract class)</h2> <p><strong>Module</strong>: Migrate (Drupal Core)<br /><strong>Class</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\DestinationBase</a><br /><strong>Extends</strong>: <a href="">Drupal\Core\Plugin\PluginBase</a></p> <p>This abstract class is extended by many migrate destination plugins. This means that the provided configuration keys apply to any destination plugin extending it.</p> <p>List of configuration keys:</p> <ol><li><strong>destination_module</strong>: An optional string value. Identifies the module handling the destination data. If not set, the Migrate API tries to read the value from the destination plugin definition.</li> </ol><h2 id="Entity">Entity (abstract class)</h2> <p><strong>Module</strong>: Migrate (Drupal Core) <strong>Plugin ID</strong>: entity:$entity_type See below.<br /><strong>Class</strong>: <a href="!modules!migrate!src!Plugin!migrate!destination!Entity.php/class/Entity/9.0.x">Drupal\migrate\Plugin\migrate\destination\Entity</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\DestinationBase</a><br /><strong>Inherited configuration options</strong>: destination_module.<br /><strong>Related article</strong>: <a href="">Drupal migrations reference: List of properties per content entity</a></p> <p>This abstract class is extended by migrate destination plugins that want to import entities. It uses the <a href="">MigrateEntity derivative</a> to handle both content and configuration entities. The derivative sets the plugin ID to entity:$entity_type where $entity_type is the machine name of the entity. For example, entity:node and entity:node_type.</p> <p>By default, content entities are handled by the <a href="">EntityContentBase</a> class while configuration entities use <a href="">EntityConfigBase</a>. Some entities like user (content) and node type (configuration) use specific classes for the import operation. The <a href="">DefaultPluginManager::getDefinitions</a> method triggers the search for classes that will override the default for a particular plugin ID. The override ultimately happens in the <a href="">DerivativeDiscoveryDecorator::getDerivatives</a> method.</p> <p>In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys, which will be available to its derivatives:</p> <ol><li><strong>default_bundle</strong>: An optional string value. Gets the bundle for the row taking into account the default. If not present, the bundle <a href="">entity key</a> should be set in the process section for entities that can have more than one bundle. For example, the type property for <a href="">nodes</a>, the vid property for <a href="">taxonomy terms</a>, and the bundle property for <a href="">media entities</a>.</li> </ol><h2 id="EntityContentBase">EntityContentBase</h2> <p><strong>Module</strong>: Migrate (Drupal Core) <strong>Plugin ID</strong>: entity:$entity_type See below.<br /><strong>Class</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityContentBase</a><br /><strong>Extends</strong>: <a href="!modules!migrate!src!Plugin!migrate!destination!Entity.php/class/Entity/9.0.x">Drupal\migrate\Plugin\migrate\destination\Entity</a><br /><strong>Inherited configuration options</strong>: destination_module and <strong>default_bundle</strong>.</p> <p>This class is used to handle import operations for all content entities, unless a specific class exists for the plugin ID being used. For example, nodes are handled by this class, but users leverage the <a href="">EntityUser</a> class. The <a href="">MigrateEntity derivative</a> sets the plugin ID to entity:$entity_type where $entity_type is the machine name of the content entity. For example, entity:node, entity:user, entity:taxonomy_term, entity:file, entity:media, entity:comment, entity:block_content, and entity:contact_message.</p> <p>In addition to the keys provided in the parent class chain, this class provides the following configuration keys:</p> <ol><li><strong>translations</strong>: An optional boolean value. It indicates if the entity is translatable, defaults to FALSE. If set to TRUE, the corresponding langcode <a href="">entity key</a> will be added to the <a href="">list of destination IDs</a> that uniquely identifies each record. If the migration itself does not provide a value for the langcode property, the site’s default language is used.</li> <li><strong>overwrite_properties</strong>: An optional array of string values. It is a list of properties or fields in the destination entity that will be overwritten if an entity with the same ID already exists. Any properties that are not listed will not be overwritten. Refer to the <a href="">class documentation</a> for an example.</li> <li><strong>validate</strong>: An optional boolean value. It indicates whether an entity should be validated, defaults to FALSE. This was <a href="">introduced in Drupal 8.8</a> and can be used to prevent invalid entities from being migrated. For example, a node with an empty title would fail validation. A required field that is not set by the migration will trigger a validation error, unless the field is configured to have a default value. Similarly, an integer field with minimum and maximum values will trigger an error if a value outside that range tries to be imported. <a href="">Field API validations</a> are triggered when this configuration option is set to TRUE.</li> </ol><h2 id="EntityUser">EntityUser</h2> <p><strong>Module</strong>: User (Drupal Core) <strong>Plugin ID</strong>: entity:user See below.<br /><strong>Class</strong>: <a href="">Drupal\user\Plugin\migrate\destination\EntityUser</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityContentBase</a><br /><strong>Inherited configuration options</strong>: destination_module, default_bundle, translations, overwrite_properties, and <strong>validate</strong>.</p> <p>This class provides a destination plugin for migrating user entities. It performs extra checks like preventing that the password for user 1 is overridden by the migration.</p> <p>In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys:</p> <ol><li><strong>md5_passwords</strong>: An optional boolean value. If set to TRUE, the pass (password) property of the user entity is assumed to contain an MD5 hash. The <a href="">passwords will be salted and re-hashed</a> before they are saved to the destination Drupal database.</li> </ol><h2 id="EntityRevision">EntityRevision</h2> <p><strong>Module</strong>: Migrate (Drupal Core) <strong>Plugin ID</strong>: entity_revision:$entity_type See below.<br /><strong>Class</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityRevision</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityContentBase</a><br /><strong>Inherited configuration options</strong>: destination_module, <strong>default_bundle</strong>, translations, overwrite_properties, and validate.</p> <p>This class provides an entity revision destination plugin. Only revisionable entities, those that define a revision <a href="">entity key</a>, can use this destination plugin. It uses the <a href="">MigrateEntityRevision derivative</a> which sets the plugin ID to entity_revision:$entity_type where $entity_type is the machine name of the entity. For example, entity_revision:node whose revision key is vid and entity_revision:block_content whose revision key is revision_id.</p> <p>Entity revisions can only be migrated after the entity to which they belong has been migrated. For example, revisions of a given node (entity_revision:node destination migration) can be migrated only after the current version of that node (entity:node destination migration) has been imported.</p> <h2 id="EntityReferenceRevisions">EntityReferenceRevisions</h2> <p><strong>Module</strong>: <a href="">Entity Reference Revisions module</a> <strong>Plugin ID</strong>: entity_reference_revisions:$entity_type See below.<br /><strong>Class</strong>: <a href="">Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityRevision</a><br /><strong>Inherited configuration options</strong>: destination_module, default_bundle, translations, overwrite_properties, and validate.<br /><strong>Related article</strong>: Introduction to paragraphs migrations in Drupal</p> <p>This class provides an entity revision revision destination plugin. It uses the <a href="">MigrateEntityReferenceRevisions derivative</a> which sets the plugin ID to entity_reference_revisions:$entity_type where $entity_type is the machine name of the entity. For example, entity_reference_revisions:node and entity_reference_revisions:paragraph. For example, entity_reference_revisions:node whose revision key is vid and entity_reference_revisions:paragraph whose revision key is revision_id.</p> <p>This is the destination plugin used for migrating <a href="">Paragraphs</a>. Entity reference fields are <a href="">no longer supported</a> to <a href="">reference Paragraphs</a>. Instead, entity entity reference revisions must be used. Therefore, this class is used for paragraphs migrations with the entity_reference_revisions:paragraph plugin ID. See <a href="">this article</a> for an example on how to migrate paragraphs.</p> <p>In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys:</p> <ol><li><strong>new_revisions</strong>: An optional boolean value. Flag to indicate if a new revision should be created instead of updating a previous default record. Only applicable when providing an entity id without a revision_id. Defaults to FALSE.</li> </ol><h2 id="EntityConfigBase">EntityConfigBase</h2> <p><strong>Module</strong>: Migrate (Drupal Core) <strong>Plugin ID</strong>: entity:$entity_id See below.<br /><strong>Class</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityConfigBase</a><br /><strong>Extends</strong>: <a href="!modules!migrate!src!Plugin!migrate!destination!Entity.php/class/Entity/9.0.x">Drupal\migrate\Plugin\migrate\destination\Entity</a><br /><strong>Inherited configuration options</strong>: destination_module and default_bundle.</p> <p>This class is used to handle import operations for all configuration entities, unless a specific class exists for the plugin ID being used. For example, taxonomy vocabularies are handled by this class, but node types leverage the <a href="">EntityNodeType</a> class. The <a href="">MigrateEntity derivative</a> sets the plugin ID to entity:$entity_type where $entity_type is the machine name of the content entity. For example, entity:node_type, entity:user_role, entity:taxonomy_vocabulary, entity:block, entity:comment_type, entity:block_content_type, entity:contact_form, entity:date_format.</p> <p>In addition to the keys provided in the parent class chain, this abstract class provides the following configuration keys:</p> <ol><li><strong>translations</strong>: An optional boolean value. if TRUE, the destination will be associated with the langcode provided by the source plugin. Defaults to FALSE. For example: en for English, es for Spanish, and fr for French.</li> </ol><h2 id="EntityNodeType">EntityNodeType</h2> <p><strong>Module</strong>: Node (Drupal Core) <strong>Plugin ID</strong>: entity:node_type<br /><strong>Class</strong>: <a href="">Drupal\node\Plugin\migrate\destination\EntityNodeType</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\EntityConfigBase</a><br /><strong>Inherited configuration options</strong>: destination_module, default_bundle, and translations.</p> <p>This class is used to import node types. It does not take extra configuration options. The plugin overrides the import method to attach the body field to the imported content type. This depends on the presence of certain destination properties in the imported row. That is, the following properties needs to be mapped in the process section of the migration:</p> <ol><li><strong>create_body</strong>: An optional boolean value. If TRUE, a body field will be added to the content type.</li> <li><strong>create_body_label</strong>: An optional string value. If set and create_body is TRUE, the value for this destination property will be used as the label of the body field.</li> </ol><h2 id="Config">Config</h2> <p><strong>Module</strong>: Migrate (Drupal Core) <strong>Plugin ID</strong>: config<br /><strong>Class</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\Config</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\DestinationBase</a><br /><strong>Inherited configuration options</strong>: destination_module.</p> <p>This class persists data to the <a href="">configuration management system</a>. In addition to the keys provided in the parent class chain, this class provides the following configuration keys:</p> <ol><li><strong>store null</strong>: An optional boolean value. If TRUE, when a property is NULL, the NULL value is stored. Otherwise, the default value is used. Defaults to FALSE. Note that there is a <em>space character in the configuration name</em>.</li> <li><strong>translations</strong>: An optional boolean value. if TRUE, the destination will be associated with the langcode provided by the source plugin. Defaults to FALSE.</li> </ol><p>Additionally, the plugin expects certain destination properties in the imported row. That is, the following properties needs to be mapped in the process section of the migration:</p> <ol><li><strong>config_name</strong>: A string value. The machine name of the configuration. For example: node.settings, node.type.article, user.settings,, and core.extension.</li> <li><strong>langcode</strong>: An optional string value. The language code of the configuration. For example: en for English, es for Spanish, and fr for French.</li> </ol><h2 id="Table">Table</h2> <p><strong>Module</strong>: Migrate Plus Plugin ID: table<br /><strong>Class</strong>: <a href="">Drupal\migrate_plus\Plugin\migrate\destination\Table</a><br /><strong>Extends</strong>: <a href="">Drupal\migrate\Plugin\migrate\destination\DestinationBase</a><br /><strong>Inherited configuration options</strong>: destination_module.</p> <p>This class allows you to write directly to a table not registered with <a href="!lib!Drupal!Core!Database!database.api.php/group/schemaapi/9.0.x">Drupal Schema API</a>. <a href="">See this test</a> for an example on how to use this plugin.</p> <p>In addition to the keys provided in the parent class chain, this class provides the following configuration keys:</p> <ol><li><strong>database_key</strong>: An string value. Key for the database connection used for inserting records. See <a href="">this documentation page</a> for more information on database connection keys. We also covered the topic when explaining the <a href="">SqlBase source plugin</a>.</li> <li><strong>table_name</strong>: An string value. Name of the table where records will be imported.</li> <li><strong>batch_size</strong>: An optional integer value. Maximum number of rows to insert in one query. Defaults to 1.</li> <li><strong>id_fields</strong>: An associative array value. Fields used to uniquely identify table rows. At least one field is required. See the class’s docblock for an example of the expected structure.</li> <li><strong>fields</strong>: An optional associative array value. Mapping of column names to values set in the process section.</li> </ol><h2 id="other-plugins">Available configuration for other migrate destination plugins</h2> <p>In Drupal core itself there are around 50 migrate destination plugins. And many more are made available by contributed modules. It would be impractical to document them all here. To get a list by yourself, load the <a href="">plugin.manager.migrate.destination</a> service and call its getDefinitions() method. This will return all migrate destination plugins provided by the <strong>modules that are currently enabled</strong> on the site. This Drush command would get the list:</p> <p><code># List of migrate destination plugin definitions. $ drush php:eval "print_r(\Drupal::service('plugin.manager.migrate.destination')-&gt;getDefinitions());" # List of migrate destination plugin ids. $ drush php:eval "print_r(array_keys(\Drupal::service('plugin.manager.migrate.destination')-&gt;getDefinitions()));"</code></p> <p>To find out which configuration options are available for any destination plugin consider the following:</p> <ul><li>Find the class that defines the plugin and find out which configuration values are read. Some plugins even include the list in the docblock of the class. Search for a pattern similar to $this-&gt;configuration['option_name'] or $configuration['option_name']. The plugins can be found in the Drupal\module_name\Plugin\migrate\destination namespace. The class itself would be in a file under the /src/Plugin/migrate/destination/ directory of the module.</li> <li>Look up in the class hierarchy. The destination plugin can use any configuration set directly in its definition class and any parent class. There might be multiple layers of inheritance.</li> <li>Check if the plugin requires the presence of specific destination properties to be set in the process section. This is usually documented in the class’ docblock, but you can also look for code like $row-&gt;getDestinationProperty('property_name').</li> </ul><p>What did you learn in today’s article? Did you know that migrate destination plugins can inherit configuration keys from their class hierarchy? Were you aware that there are so many destination plugins? Other than the ones listed here, which destination plugins have you used? Please share your answers in the comments. Also, we would be grateful if you shared this article with your friends and colleagues.</p> </div> 2020 July 15 Mauricio Dinarte