Blog https://agaric.coop/ en Good discussion here with a great takeaway. Meet your fellow workers! https://portside.org/2022-05-12/revolutionary-grounds https://agaric.coop/ <div class="flow_middle"> </div> 2022 May 13 https://agaric.coop/Good%20discussion%20here%20with%20a%20great%20takeaway.%20Meet%20your%20fellow%20workers%21%20https%3A//portside.org/2022-05-12/revolutionary-grounds Here is a social media microblog-sized post just to say we are syndicating to social.coop Mastodon and Twitter thanks to the Indieweb module! https://agaric.coop/ <div class="flow_middle"> <p>It's great to be here, and <a href="https://social.coop/@agaric">there</a>, and <a href="https://twitter.com/agaric">there</a>.</p> <p>Thanks <a href="https://www.drupal.org/project/indieweb">Indieweb module for Drupal</a>!</p> </div> 2022 March 08 Benjamin Melançon, Michele Metts https://agaric.coop/Here%20is%20a%20social%20media%20microblog-sized%20post%20just%20to%20say%20we%20are%20syndicating%20to%20social.coop%20Mastodon%20and%20Twitter%20thanks%20to%20the%20Indieweb%20module%21 Create and use a custom permission in your module https://agaric.coop/ <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img 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." loading="lazy" 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="https://api.drupal.org/api/drupal/core%21lib%21Drupal.php/function/Drupal%3A%3AcurrentUser/9.1.x">load the current user</a> if it's not, and use the <a href="https://api.drupal.org/api/drupal/core%21modules%21user%21src%21Entity%21User.php/function/User%3A%3AhasPermission/9.1.x">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="https://gitlab.com/find-it-program-locator/drutopia_findit_site_management/">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="https://www.drupal.org/node/2311427">change record for when the new approach replaced hook_permission()</a>.</p> </div> 2021 March 10 Benjamin Melançon https://agaric.coop/Create%20and%20use%20a%20custom%20permission%20in%20your%20module Why we resist Zoom and choose BigBlueButton for video chat https://agaric.coop/ <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="https://mayfirst.org">MayFirst.org</a> for most of our projects and we have a relationship with our BigBlueButton host. The <a href="https://theintercept.com/2020/03/31/zoom-meeting-encryption/">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="https://www.ncsl.org/research/telecommunications-and-information-technology/security-breach-notification-laws.aspx">https://www.ncsl.org/research/telecommunications-and-information-technology/security-breach-notification-laws.aspx</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="https://www.theguardian.com/technology/2020/apr/02/zoom-technology-security-coronavirus-video-conferencing">Zoom software to be malware</a>. Within companies that use Zoom, employers are even able to monitor whether or not you are <a href="https://www.huffpost.com/entry/zoom-tracks-not-paying-attention-video-call_l_5e7b96b5c5b6b7d80959ea96">focusing on the computer screen during meetings</a> which seems excessively intrusive. Speaking of intrusive, the <a href="https://9to5mac.com/2020/03/27/zoom-ios-app/">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="https://www.tenable.com/blog/unauthorized-call-and-webcam-access-vulnerability-in-zoom-mac-client-cve-2019-13450">vulnerabilities</a> is a good thing to pass on. Some of the <a href="https://www.schneier.com/blog/archives/2019/07/zoom_vulnerabil.html">bad stuff remains</a> even if you uninstall the Zoom app from your device! Even though <a href="https://www.cnbc.com/2020/04/08/zoom-faces-investor-lawsuit-over-privacy-and-security-flaws.html">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="https://github.com/bigbluebutton/bigbluebutton/issues">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="https://www.fsf.org/campaigns/priority-projects/voicevideochat">alternative solutions</a>. You can find some great suggestions of software and switch to it by using this site called <a href="https://switching.software">switching.software</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="https://techcrunch.com/2020/03/31/zoom-at-your-own-risk/?guccounter=1">a false sense of security</a> as it is widely used and very popular.</p> <p>Of course, there are reasons to <a href="https://blog.grobox.de/2009/ten-reasons-why-you-should-boycott-skype/">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="http://communitybridge.com">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="https://theintercept.com/2021/01/18/leak-zoom-meeting/">to leak a zoom video without being identified</a>, you need to be very careful!</li> <li><a href="https://theintercept.com/2020/11/14/zoom-censorship-leila-khaled-palestine/">Zoom censorship of Palestine seminars sparks fight over academic freedom</a></li> <li><a href="https://theintercept.com/2020/04/03/zooms-encryption-is-not-suited-for-secrets-and-has-surprising-links-to-china-researchers-discover/">Zoom encryption is not suited for secrets</a></li> <li><a href="https://theintercept.com/2020/03/31/zoom-meeting-encryption/">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="https://en.wikipedia.org/wiki/Zoom_(software)#Reception">the wikipedia page</a> has a good summary of some issues</li> <li>Stallman provides resources on <a href="https://stallman.org/zoom.html">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="https://odysee.com/$/search?q=the%20hated%20one">The Hated One</a></li> <li><a href="https://odysee.com/@RobBraxmanTech">Rob Braxman Tech</a></li> <li><a href="https://odysee.com/@the-rev:5/2018-04-27-Safe-Services:9">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 https://agaric.coop/Why%20we%20resist%20Zoom%20and%20choose%20BigBlueButton%20for%20video%20chat Drupal toolbar not working in dev environments in Firefox? Here's why. https://agaric.coop/ <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="https://developer.mozilla.org/en-US/docs/Tools/Web_Console">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="https://www.ddev.com/ddev-local/">DDEV</a>, and you working on way too many sites at once—combined with a pretty bad <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1683299">Firefox bug that will be fixed in the next release</a>.</p> <p>To quote <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1683299#c7">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. my-app.example.com and intranet.example.com).</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="https://ddev.readthedocs.io/en/stable/users/cli-usage/#using-ddev-offline-and-top-level-domain-options">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 src="/sites/default/files/styles/large/public/2021-01/toomanytabs_0.jpeg?itok=tmNHTYpV" width="480" height="360" alt="A drawing of a laptop with open tabs extending outside of the laptop as horizontally tiled windows." loading="lazy" 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="https://agaric.gitlab.io/raw-notes/">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="https://www.flickr.com/photos/38765959@N00">John Markos O'Neill</a> is licensed with <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA 2.0</a>.</p> </div> 2021 January 19 Benjamin Melançon https://agaric.coop/Drupal%20toolbar%20not%20working%20in%20dev%20environments%20in%20Firefox?Here=#039;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 https://agaric.coop/ <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img 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;" loading="lazy" 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="https://agaric.coop/drupal-upgrades-migrating-content-your-new-site">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="https://www.mydropwizard.com/drupal-6-lts">at least February 24th, 2022</a>— and Drupal 7 has a larger install base than Drupal 6 ever did, and <a href="https://www.drupal.org/project/usage/drupal">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 src="/sites/default/files/styles/large/public/2021-01/usage-statistics-for-Drupal-core.png?itok=JouW1YhU" width="480" height="261" 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." loading="lazy" 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="https://agaric.coop/ask">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="https://agaric.coop/drupal-upgrades-migrating-content-your-new-site">upgrade your site into the era of modern Drupal</a>.</p> </div> 2021 January 13 Benjamin Melançon https://agaric.coop/Upgrade%20from%20Drupal%206%20or%207%20when%20it%20is%20right%20for%20you%3A%20In%20the%20era%20of%20modern%20Drupal%2C%20release%20cycles%20of%20major%20versions%20have%20only%20minor%20importance Community-managed categories https://agaric.coop/ <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="https://www.drupal.org/project/cmt">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="https://agaric.coop/ask">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 https://agaric.coop/Community-managed%20categories Double-edged Raiser: Past time to ditch Blackbaud https://agaric.coop/ <section class="hero is-white hero-project"> <div class="container"> <div class="hero-image"> <img 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)." loading="lazy" 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="https://civicrm.org/">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="https://www.thenonprofittimes.com/npt_articles/blackbaud-s-buying-spree-hits-431-million-with-convio-deal/">eTapestry, Kintera, and Convio-swallowing</a> monopolist Blackbaud, you probably can afford to.</p> <p>At <a href="https://agaric.coop">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="https://palantetech.coop/">Palante Technology Cooperative</a> and <a href="https://www.mydropwizard.com/">myDropWizard</a> are well-versed in CiviCRM, as are <a href="https://civicrm.org/partners-contributors">many others</a>. Please consider this when weighing your options for maintaining a strong, ethical relationship with your supporters, and <a href="https://agaric.coop/ask">let us know</a> if you have any thoughts or questions!</p> </div> 2020 August 03 Benjamin Melançon https://agaric.coop/Double-edged%20Raiser%3A%20Past%20time%20to%20ditch%20Blackbaud Are you building communities of practice for community health in this time of public recognition of injustice? https://agaric.coop/ <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="https://www.usnews.com/news/healthiest-communities/articles/2020-07-08/racism-resonates-as-public-health-crisis-amid-pandemic-protests">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="https://portside.org/2016-01-23/environmental-racism-harms-americans-flint-and-beyond">forced into the national conversation by Flint, Michigan</a> years ago and <a href="https://www.propublica.org/article/on-the-minds-of-black-lives-matters-protestors-a-racist-health-system">COVID-19 has made long-standing problems of racism in healthcare provision</a> impossible to ignore.  The <a href="https://itsgoingdown.org/mapping-the-states-strategy-of-repression-against-the-rebellion/">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="https://twitter.com/BullyCreative">D.A. Bullock</a>, indeed, <a href="https://www.tcdailyplanet.net/entrenched-sexism-racism-police/">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="https://www.nichq.org/insight/raising-bar-virtual-qi-collaboration">a client giving us a shout out</a>, Agaric has come to acknowledge <a href="https://agaric.coop/health">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="https://www.yesmagazine.org/opinion/2020/07/08/history-protests-social-change/">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 https://agaric.coop/Are%20you%20building%20communities%20of%20practice%20for%20community%20health%20in%20this%20time%20of%20public%20recognition%20of%20injustice Drupal migrations reference: List of configuration options for destination plugins https://agaric.coop/ <div class="flow_middle"> <p>In the previous article we provided a <a href="https://agaric.coop/blog/drupal-migrations-reference-list-configuration-options-source-plugins">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21DestinationBase.php/class/DestinationBase/9.0.x">Drupal\migrate\Plugin\migrate\destination\DestinationBase</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Plugin%21PluginBase.php/class/PluginBase/9.0.x">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="https://api.drupal.org/api/drupal/core!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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21DestinationBase.php/class/DestinationBase/9.0.x">Drupal\migrate\Plugin\migrate\destination\DestinationBase</a><br /> <strong>Inherited configuration options</strong>: destination_module.<br /> <strong>Related article</strong>: <a href="https://agaric.coop/blog/drupal-migrations-reference-list-properties-content-entity">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21Derivative%21MigrateEntity.php/class/MigrateEntity/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityContentBase.php/class/EntityContentBase/9.0.x">EntityContentBase</a> class while configuration entities use <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityConfigBase.php/class/EntityConfigBase/9.0.x">EntityConfigBase</a>. Some entities like user (content) and node type (configuration) use specific classes for the import operation. The <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Plugin%21DefaultPluginManager.php/function/DefaultPluginManager%3A%3AgetDefinitions/9.0.x">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="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Plugin%21Discovery%21DerivativeDiscoveryDecorator.php/function/DerivativeDiscoveryDecorator%3A%3AgetDerivatives/9.0.x">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="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityType.php/property/EntityType%3A%3Aentity_keys/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Entity%21Node.php/class/Node/9.0.x">nodes</a>, the vid property for <a href="https://api.drupal.org/api/drupal/core%21modules%21taxonomy%21src%21Entity%21Term.php/class/Term/9.0.x">taxonomy terms</a>, and the bundle property for <a href="https://api.drupal.org/api/drupal/core%21modules%21media%21src%21Entity%21Media.php/class/Media/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityContentBase.php/class/EntityContentBase/9.0.x">Drupal\migrate\Plugin\migrate\destination\EntityContentBase</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core!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="https://api.drupal.org/api/drupal/core%21modules%21user%21src%21Plugin%21migrate%21destination%21EntityUser.php/class/EntityUser/9.0.x">EntityUser</a> class. The <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21Derivative%21MigrateEntity.php/class/MigrateEntity/9.0.x">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="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityType.php/property/EntityType%3A%3Aentity_keys/9.0.x">entity key</a> will be added to the <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityContentBase.php/function/EntityContentBase%3A%3AgetIds/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityContentBase.php/class/EntityContentBase/9.0.x">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="https://www.drupal.org/node/3073707">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="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21FieldableEntityInterface.php/function/FieldableEntityInterface%3A%3Avalidate/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21user%21src%21Plugin%21migrate%21destination%21EntityUser.php/class/EntityUser/9.0.x">Drupal\user\Plugin\migrate\destination\EntityUser</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityContentBase.php/class/EntityContentBase/9.0.x">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="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Password%21PhpassHashedPassword.php/function/PhpassHashedPassword%3A%3Ahash/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityRevision.php/class/EntityRevision/9.0.x">Drupal\migrate\Plugin\migrate\destination\EntityRevision</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityContentBase.php/class/EntityContentBase/9.0.x">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="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityType.php/property/EntityType%3A%3Aentity_keys/9.0.x">entity key</a>, can use this destination plugin. It uses the <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21Derivative%21MigrateEntityRevision.php/class/MigrateEntityRevision/9.0.x">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="https://www.drupal.org/project/entity_reference_revisions">Entity Reference Revisions module</a> <strong>Plugin ID</strong>: entity_reference_revisions:$entity_type See below.<br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/entity_reference_revisions/-/blob/8.x-1.x/src/Plugin/migrate/destination/EntityReferenceRevisions.php">Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityRevision.php/class/EntityRevision/9.0.x">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="https://git.drupalcode.org/project/entity_reference_revisions/-/tree/8.x-1.x/src/Plugin/Derivative">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="https://www.drupal.org/project/paragraphs">Paragraphs</a>. Entity reference fields are <a href="https://git.drupalcode.org/project/paragraphs/-/commit/ab0011ddf98e98c314ff68cce055c588a8742cb0">no longer supported</a> to <a href="https://www.drupal.org/node/2668678">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="https://agaric.coop/blog/introduction-paragraphs-migrations-drupal">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityConfigBase.php/class/EntityConfigBase/9.0.x">Drupal\migrate\Plugin\migrate\destination\EntityConfigBase</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core!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="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21destination%21EntityNodeType.php/class/EntityNodeType/9.0.x">EntityNodeType</a> class. The <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21Derivative%21MigrateEntity.php/class/MigrateEntity/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21destination%21EntityNodeType.php/class/EntityNodeType/9.0.x">Drupal\node\Plugin\migrate\destination\EntityNodeType</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21EntityConfigBase.php/class/EntityConfigBase/9.0.x">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21Config.php/class/Config/9.0.x">Drupal\migrate\Plugin\migrate\destination\Config</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21DestinationBase.php/class/DestinationBase/9.0.x">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="https://www.drupal.org/docs/8/configuration-management">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, system.site, 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="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate/destination/Table.php">Drupal\migrate_plus\Plugin\migrate\destination\Table</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21DestinationBase.php/class/DestinationBase/9.0.x">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="https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!database.api.php/group/schemaapi/9.0.x">Drupal Schema API</a>. <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/tests/src/Kernel/MigrateTableTest.php">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="https://www.drupal.org/docs/8/api/database-api/database-configuration">this documentation page</a> for more information on database connection keys. We also covered the topic when explaining the <a href="https://agaric.coop/blog/drupal-migrations-reference-list-configuration-options-source-plugins#SqlBase">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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21MigrateDestinationPluginManager.php/class/MigrateDestinationPluginManager/9.0.x">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 https://agaric.coop/Drupal%20migrations%20reference%3A%20List%20of%20configuration%20options%20for%20destination%20plugins Drupal migrations reference: List of configuration options for source plugins https://agaric.coop/ <div class="flow_middle"> <p>In a previous article we explained <a href="https://agaric.coop/blog/understanding-syntax-drupal-migrations">the syntax used to write Drupal migration</a>. We also provided references of <a href="https://agaric.coop/blog/drupal-migrations-reference-list-subfields-field-type">subfields</a> and <a href="https://agaric.coop/blog/drupal-migrations-reference-list-properties-content-entity">content entities' properties</a> including those provided by the <a href="https://agaric.coop/blog/drupal-migrations-reference-list-properties-commerce-content-entity">Commerce module</a>. This time we are going to list the configuration options of many migrate source plugins. For example, when importing from a JSON file you need to specify which data fetcher and parser to use. In the case of CSV migrations, the source plugin configuration changes depending on the presence of a headers row. Finding out which options are available might require some Drupal development knowledge. To make the process easier, in today’s article we are presenting a reference of available configuration options for migrate source plugins provided by Drupal core and some contributed modules.</p> <p><img alt="List of configuration options for source plugins" data-entity-type="file" data-entity-uuid="908400f4-5c34-45e4-b277-d934792319f5" src="/sites/default/files/inline-images/migrations-source-plugin-configuration-options-list.jpg" width="960" height="540" /></p> <p>For each migrate source 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="SourcePluginBase">SourcePluginBase (abstract class)</h2> <p><strong>Module</strong>: Migrate (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Plugin%21PluginBase.php/class/PluginBase/9.0.x">Drupal\Core\Plugin\PluginBase</a></p> <p>This abstract class is extended by most migrate source plugins. This means that the provided configuration keys apply to any source plugin extending it.</p> <p>List of configuration keys:</p> <ol> <li><strong>skip_count</strong>: An optional boolean value. If set, do not attempt to count the source. This includes status and import operations.</li> <li><strong>cache_counts</strong>: An optional boolean value. If set, cache the source count. This saves time calculating the number of available records in the source when a count operation is requested.</li> <li><strong>cache_key</strong>: An optional string value. Uniquely named cache key used for cache_counts.</li> <li><strong>track_changes</strong>: An optional boolean value. If set, the Migrate API will keep a hash of the source rows to determine whether the incoming data has changed. If the hash is different the record is re-imported.</li> <li><strong>high_water_property</strong>: An optional array value. If set, only content with a higher value will be imported. The value is usually a timestamp or serial ID indicating what was the last imported record. This key is configured as an associate array. The name key indicates the column in the source that will be used for the comparison. The alias key is optional and if set it serves as a table alias for the column name.</li> <li><strong>source_module</strong>: An optional string value. Identifies the system providing the data the source plugin will read. If not set, the Migrate API tries to read the value from the source plugin annotation. The source plugin itself determines how the value is used. For example, Migrate Drupal's source plugins expect source_module to be the name of a module that must be installed and enabled in the source database.</li> </ol> <p>The high_water_property and track_changes are mutually exclusive. They are both designed to conditionally import new or updated records from the source. Hence, only one can be configured per migration definition file.</p> <h2 id="SqlBase">SqlBase (abstract class)</h2> <p><strong>Module</strong>: Migrate (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SqlBase.php/class/SqlBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SqlBase</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This abstract class is extended by migrate source plugins whose data may be fetched via a database connection. This means that the provided configuration keys apply to any source plugin extending it.</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>key</strong>: An optional string value. The database key name. Defaults to 'migrate'.</li> <li><strong>target</strong>: An optional string value. The database target name. Defaults to 'default'.</li> <li><strong>database_state_key</strong>: An optional string value. Name of the state key which contains an array with database connection information. The Migrate API will consult the <a href="https://www.drupal.org/docs/8/api/state-api/overview">States API</a> using the provided key. The returned value should be an associative array with at least two keys: key and target to determine which database connection to use. A third key database can also be included containing database connection information as seen in the snippet below.</li> <li><strong>batch_size</strong>: An optional integer value. Number of records to fetch from the database during each batch. If omitted, all records are fetched in a single query.</li> <li><strong>ignore_map</strong>: An optional boolean value. Source data is joined to the map table by default to improve performance. If set to TRUE, the map table will not be joined. Using expressions in the query may result in column aliases in the JOIN clause which would be invalid SQL. If you run into this, set ignore_map to TRUE.</li> </ol> <p>To explain how these configuration keys are used, consider the following database connections:</p> <p><code>&lt;?php $databases['default']['default'] = [ 'database' =&gt; 'drupal-8-or-9-database-name', 'username' =&gt; 'drupal-8-or-9-database-username', 'password' =&gt; 'drupal-8-or-9-database-password', 'host' =&gt; 'drupal-8-or-9-database-server', 'port' =&gt; '3306', 'namespace' =&gt; 'Drupal\\Core\\Database\\Driver\\mysql', 'driver' =&gt; 'mysql', ]; $databases['migrate']['default'] = [ 'database' =&gt; 'drupal-6-or-7-database-name', 'username' =&gt; 'drupal-6-or-7-database-username', 'password' =&gt; 'drupal-6-or-7-database-password', 'host' =&gt; 'drupal-6-or-7-database-server', 'port' =&gt; '3306', 'namespace' =&gt; 'Drupal\\Core\\Database\\Driver\\mysql', 'driver' =&gt; 'mysql', ];</code></p> <p>This snippet can be added to settings.php or settings.local.php. The $databases array is a nested array of at least three levels. The first level defines the database keys: default and migrate in our example. The second level defines the database targets: default in both cases. The third level is an array with connection details for each key/target combination. This <a href="https://www.drupal.org/docs/8/api/database-api/database-configuration">documentation page</a> contains more information about database configuration.</p> <p>Based on the specified configuration values, this is how the Migrate API determines which database connection to use:</p> <ul> <li>If the source plugin configuration contains database_state_key, its value is taken as the name of a States API key that specifies an array with the database configuration.</li> <li>Otherwise, if the source plugin configuration contains key, the database configuration with that name is used.</li> <li>Otherwise, load a fallback state key from the States API. The value that it tries to read is the global state key: migrate.fallback_state_key.</li> <li>Otherwise, the database connection named migrate is used by default.</li> <li>If all of the above steps fail, a RequirementsException is thrown.</li> </ul> <p>Note that all values configuration keys are optional. If none is set, the plugin will default to use the connection specified under $databases['migrate']['default']. At least, set the key configuration even if the value is migrate. This would make it explicit which connection is being used.</p> <h2 id="DrupalSqlBase">DrupalSqlBase (abstract class)</h2> <p><strong>Module</strong>: Migrate Drupal (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21DrupalSqlBase.php/class/DrupalSqlBase/9.0.x">Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SqlBase.php/class/SqlBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SqlBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, source_module, key, target, database_state_key, batch_size, and ignore_map.</p> <p>This abstract class provides general purpose helper methods that are commonly needed when writing source plugins that use a Drupal database as a source. For example, check if the given module exists and read Drupal configuration variables. Check the linked class documentation for more available methods.</p> <p>In addition to the keys provided in the parent class chain, this abstract class provides the following configuration key:</p> <ol> <li><strong>constants</strong>: An optional array value used to add module dependencies. The value is an associative array with two possible elements. The first uses the entity_type key to add a dependency on the module that provides the specified entity type. The second uses the module key to directly add a dependency on the specified module. In both cases, the <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21DependencyTrait.php/trait/DependencyTrait/9.0.x">DependencyTrait</a> is used for setting the dependencies.</li> </ol> <p><strong>Warning</strong>: A plugin extending this abstract class might want to use this configuration key in the source definition to set module dependencies. If so, the expected keys might clash with other <a href="https://understanddrupal.com/articles/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#source-constants">source constants used in the process pipeline</a>. Arrays keys in PHP are case sensitive. Using uppercase in custom source constants might avoid this clash, but it is preferred to use a different name to avoid confusion.</p> <p>This abstract class is extended by dozens of <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21DrupalSqlBase.php/class/uses/DrupalSqlBase/9.0.x">core classes that provide an upgrade path from Drupal 6 and 7</a>. It is also used by the <a href="https://www.drupal.org/project/commerce_migrate">Commerce Migrate module</a> to read <a href="https://git.drupalcode.org/project/commerce_migrate/-/blob/8.x-2.x/modules/commerce/src/Plugin/migrate/source/commerce1/ProductType.php">product types</a>, <a href="https://git.drupalcode.org/project/commerce_migrate/-/blob/8.x-2.x/modules/commerce/src/Plugin/migrate/source/commerce1/ProductDisplayType.php">product display types</a>, and <a href="https://git.drupalcode.org/project/commerce_migrate/-/blob/8.x-2.x/modules/commerce/src/Plugin/migrate/source/commerce1/ShippingFlatRate.php">shipping flat rates</a> from a Commerce 1 database. The same module follows a similar approach to <a href="https://git.drupalcode.org/project/commerce_migrate/-/tree/8.x-2.x/modules/ubercart/src/Plugin/migrate/source">read data from an Ubercart database</a>. The <a href="https://www.drupal.org/project/paragraphs">Paragraphs module</a> also <a href="https://git.drupalcode.org/project/paragraphs/-/blob/8.x-1.x/src/Plugin/migrate/source/DrupalSqlBase.php">extends it</a> to add and implement <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Plugin%21ConfigurableInterface.php/interface/ConfigurableInterface/9.0.x">Configurable Plugin interface</a> so it can import <a href="https://git.drupalcode.org/project/paragraphs/-/blob/8.x-1.x/src/Plugin/migrate/source/d7/FieldCollectionType.php">field collection types</a> and <a href="https://git.drupalcode.org/project/paragraphs/-/blob/8.x-1.x/src/Plugin/migrate/source/d7/ParagraphsType.php">paragraphs types</a> from Drupal 7.</p> <h2 id="d8_config">Config</h2> <p><strong>Module</strong>: Migrate Drupal (Drupal Core). <strong>Plugin ID</strong>: d8_config<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21d8%21Config.php/class/Config/9.0.x">Drupal\migrate_drupal\Plugin\migrate\source\d8\Config</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21DrupalSqlBase.php/class/DrupalSqlBase/9.0.x">Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, source_module, key, target, database_state_key, batch_size, ignore_map, and constants.</p> <p>This plugin allows reading configuration values from a Drupal 8 site by reading its config table.</p> <p>In addition to the keys provided in the parent class chain, this plugin does not define extra configuration keys. And example configuration for this plugin would be:</p> <p><code>source: plugin: d8_config key: migrate skip_count: true</code></p> <p>In this case we are setting the key property from SqlBase to use the migrate default database connection. The skip_count from SourcePluginBase indicates that there is no need to count how many records exist in the source database before executing migration operations like importing them.</p> <p>This plugin is presented to show that Drupal core already offers a way to migrate data from Drupal 8. Remember that there are dozens of other plugins extending DrupalSqlBase. It would be impractical to list them all here. See this <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21DrupalSqlBase.php/class/uses/DrupalSqlBase/9.0.x">API page</a> for a list of all of them.</p> <h2 id="csv">CSV</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/migrate_source_csv">Migrate Source CSV</a>. <strong>Plugin ID</strong>: csv<br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/migrate_source_csv/-/blob/8.x-3.x/src/Plugin/migrate/source/CSV.php">Drupal\migrate_source_csv\Plugin\migrate\source\CSV</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This plugin allows reading data from a CSV file. We used this plugin in the <a href="https://agaric.coop/blog/migrating-csv-files-drupal">CSV migration example</a> of the <a href="https://agaric.coop/31-days-drupal-migrations">31 days of migration series</a>.</p> <p>In addition to the keys provided in the parent class chain, this plugin provides the following configuration keys:</p> <ol> <li><strong>path</strong>: A string value. It contains the path to the CSV file. Starting with the 8.x-3.x branch, <a href="https://api.drupal.org/api/drupal/namespace/Drupal!Core!StreamWrapper/9.0.x">stream wrappers</a> are supported. The original article contains more details on specifying the <a href="https://agaric.coop/blog/migrating-csv-files-drupal#file-location">location of the CSV file</a>.</li> <li><strong>ids</strong>: An array of string values. The column names listed are used to uniquely identify each record.</li> <li><strong>header_offset</strong>: An optional integer value. The index of record to be used as the CSV header and the thereby each record's field name. It defaults to zero (0) because the index is zero-based. For CSV files with no header row the value should be set to null.</li> <li><strong>fields</strong>: An optional associative array value. It contains a nested array of names and labels to use instead of a header row. If set, it will overwrite the column names obtained from header_offset.</li> <li><strong>delimiter</strong>: An optional string value. It contains a one character column delimiter. It defaults to a comma (,). For example, if your file uses tabs as delimiter, you set this configuration to \t.</li> <li><strong>enclosure</strong>: An optional string value. It contains one character used to enclose the column values. Defaults to double quotation marks (").</li> <li><strong>escape</strong>: An optional string value. It contains one character used for character escaping in the column values. It defaults to a backslash (\).</li> </ol> <p><strong>Important</strong>: The configuration options changed significantly between the 8.x-3.x and 8.x-2.x branches. Refer to this <a href="https://www.drupal.org/node/3060246">change record</a> for a reference of how to configure the plugin for the 8.x-2.x.</p> <p>For reference, below is the source plugin configuration used in the <a href="https://agaric.coop/blog/migrating-csv-files-drupal">CSV migration example</a>:</p> <p><code>source: plugin: csv path: modules/custom/ud_migrations/ud_migrations_csv_source/sources/udm_photos.csv ids: [photo_id] header_offset: null fields: - name: photo_id label: 'Photo ID' - name: photo_url label: 'Photo URL'</code></p> <h2 id="spreadsheet">Spreadsheet</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/migrate_spreadsheet">Migrate Spreadsheet</a>. <strong>Plugin ID</strong>: spreadsheet<br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/migrate_spreadsheet/-/blob/8.x-1.x/src/Plugin/migrate/source/Spreadsheet.php">Drupal\migrate_spreadsheet\Plugin\migrate\source\Spreadsheet</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This plugin allows reading data from Microsoft Excel and LibreOffice Calc files. It requires the <a href="https://github.com/PHPOffice/PhpSpreadsheet">PhpOffice/PhpSpreadsheet</a> library <strong>and</strong> many PHP extensions including ext-zip. Check <a href="https://github.com/PHPOffice/PhpSpreadsheet/blob/master/composer.json#L41">this page</a> for a full list of dependencies. We used this plugin in the <a href="https://agaric.coop/blog/migrating-microsoft-excel-and-libreoffice-calc-files-drupal">spreadsheet migration examples</a> of the <a href="/31-days-of-migrations">31 days of migration series</a>.</p> <p>In addition to the keys provided in the parent class chain, this plugin provides the following configuration keys:</p> <ol> <li><strong>file</strong>: A string value. It stores the path to the document to process. You can use a relative path from the Drupal root, an absolute path, or <a href="https://agaric.coop/blog/migrating-csv-files-drupal#file-location">stream wrappers</a>.</li> <li><strong>worksheet</strong>: A string value. It contains the name of the one worksheet to process.</li> <li><strong>header_row</strong>: An optional integer value. This number indicates which row contains the headers. Contrary to <a href="https://agaric.coop/blog/migrating-csv-files-drupal">CSV migrations</a>, the row number is not zero-based. So, set this value to 1 if headers are on the first row, 2 if they are on the second, and so on.</li> <li><strong>origin</strong>: An optional string value. It defaults to A2. It indicates which non-header cell contains the first value you want to import. It assumes a grid layout and you only need to indicate the position of the top-left cell value.</li> <li><strong>columns</strong>: An optional array value. It is the list of columns you want to make available for the migration. In case of files with a header row, use those header values in this list. Otherwise, use the default title for columns: A, B, C, etc. If this setting is missing, the plugin will return all columns. This is not ideal, especially for very large files containing more columns than needed for the migration.</li> <li><strong>row_index_column</strong>: An optional string value. This is a special column that contains the row number for each record. This can be used as a unique identifier for the records in case your dataset does not provide a suitable value. Exposing this special column in the migration is up to you. If so, you can come up with any name as long as it does not conflict with header row names set in the columns configuration. Important: this is an autogenerated column, not any of the columns that comes with your dataset.</li> <li><strong>keys</strong>: An optional associative array value. If not set, it defaults to the value of row_index_column. It contains the "columns" that uniquely identify each record. The keys are column names as defined in the data_rows key. The values is an array with a single member with key 'type' and value a column type such as 'integer'. For files with a header row, you can use the values set in the columns configuration. Otherwise, use default column titles like A, B, C, etc. In both cases, you can use the row_index_column column if it was set.</li> </ol> <p>Note that nowhere in the plugin configuration you specify the file type. The same setup applies for both Microsoft Excel and LibreOffice Calc files. The library will take care of detecting and validating the proper type.</p> <p>For reference, below is the source plugin configuration used in the <a href="https://agaric.coop/blog/migrating-microsoft-excel-and-libreoffice-calc-files-drupal#header-row">LibreOffice Calc migration example</a>:</p> <p><code>source: plugin: spreadsheet file: modules/custom/ud_migrations/ud_migrations_sheets_sources/sources/udm_book_paragraph.ods worksheet: 'UD Example Sheet' header_row: 1 origin: A2 columns: - book_id - book_title - 'Book author' row_index_column: 'Document Row Index' keys: book_id: type: string</code></p> <h2 id="SourcePluginExtension">SourcePluginExtension (abstract class)</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/migrate_plus">Migrate Plus</a><br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate/source/SourcePluginExtension.php">Drupal\migrate_plus\Plugin\migrate\source\SourcePluginExtension</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This abstract class provides extra configuration keys. It is extended by the URL plugin (explained later) and by source plugins provided by other modules like <a href="https://www.drupal.org/project/feeds_migrate">Feeds Migrate</a>.</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>fields</strong>: An associative array value. Each element represents a field that will be made available to the migration. The following options can be set:</li> <li><strong>name</strong>: A string value. This is how the field is going to be referenced in the migration. The name itself can be arbitrary. If it contains spaces, you need to put double quotation marks (") around it when referring to it in the migration.</li> <li><strong>label</strong>: An optional string value. This is a description used when presenting details about the migration. For example, in the user interface provided by the Migrate Tools module. When defined, you do not use the label to refer to the field. Keep using the name.</li> <li><strong>selector</strong>: A string value. This is another XPath-like string to find the field to import. The value must be relative to the location specified by the item_selector configuration. In the example, the fields are direct children of the records to migrate. Therefore, only the property name is specified (e.g., unique_id). If you had nested objects or arrays, you would use a slash (/) character to go deeper in the hierarchy. This will be demonstrated in the image and paragraph migrations.</li> <li><strong>ids</strong>: An associative array value. It contains the "columns" that uniquely identify each record. The keys are column names as defined in the data_rows key. The values is an array with a single member with key 'type' and value a column type such as 'integer'.</li> </ol> <p>See the code snippet for the Url plugin in the next section for an example of how these configuration options are used.</p> <h2 id="url">Url (used for JSON, XML, SOAP, and Google Sheets migrations)</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/migrate_plus">Migrate Plus</a>. <strong>Plugin ID</strong>: url<br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate/source/Url.php">Drupal\migrate_plus\Plugin\migrate\source\Url</a><br /> <strong>Extends</strong>: <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate/source/SourcePluginExtension.php">Drupal\migrate_plus\Plugin\migrate\source\SourcePluginExtension</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, source_module, <strong>fields</strong>, and <strong>ids</strong>.</p> <p>This plugin allows reading data from URLs. Using data parser plugins it is possible to fetch data from <a href="https://agaric.coop/blog/migrating-json-files-drupal">JSON</a>, <a href="https://agaric.coop/blog/migrating-xml-files-drupal">XML</a>, SOAP, and <a href="https://agaric.coop/blog/migrating-google-sheets-drupal">Google Sheets</a>. Note that this source plugin uses other plugins provided by Migrate Plus that might require extra configuration keys in addition to the ones explicitly defined in the plugin class. Those will also be listed.</p> <p>In addition to the keys provided in the parent class chain, this plugin provides the following configuration keys:</p> <ol> <li><strong>urls</strong>: An array of string values. It contains the source URLs to retrieve. If the file data fetcher plugin is used, the location can be a relative path from the Drupal root, an absolute path, a fully-qualified URL, or a stream wrapper. See this article for details on <a href="https://agaric.coop/blog/migrating-json-files-drupal#file-location">location of the JSON file</a>. If the HTTP data fetcher plugin is used, the value can use any protocol supported by curl. See this article for more details on importing <a href="https://agaric.coop/blog/migrating-json-files-drupal#remote-file">remote JSON files</a>.</li> <li><strong>data_parser_plugin</strong>: A string value. It indicates which data parser plugin to use. Possible values provided by the Migrate Plus module are json, xml, simple_xml, and soap. If the <a href="https://www.drupal.org/project/migrate_google_sheets">Migrate Google Sheets module</a> is installed, it is possible to set the value to google_sheets. Review the relevant articles in the <a href="/31-days-of-migrations">31 days of migration series</a> to know more about how each of them are used.</li> </ol> <p>The <strong>data parser plugins</strong> provide the following configuration keys:</p> <ol> <li><strong>item_selector</strong>: A string value. It indicates where in the source file lies the array of records to be migrated. Its value is an XPath-like string used to traverse the file hierarchy. Note that a slash (/) is used to separate each level in the hierarchy.</li> <li><strong>data_fetcher_plugin</strong>: A string value. It indicates which data parser plugin to use. Possible values provided by the Migrate Plus module are file and http.</li> </ol> <p>The <strong>HTTP data fetcher plugins</strong> provide the following configuration keys:</p> <ol> <li><strong>headers</strong>: An associative array value. The key/value pairs represent HTTP headers to be sent when the request is made.</li> <li><strong>authentication</strong>: An associative array value. One of the elements in the array should be the plugin key which indicates the authentication plugin to use. Possible values provided by the Migrate Plus module are basic, digest, and oauth2. Other elements in the array will depend on the selected authentication plugin.</li> </ol> <p>The <strong>basic and digest authentication plugins</strong> provide the following configuration keys:</p> <ol> <li><strong>username</strong>: A string value.</li> <li><strong>password</strong>: A string value.</li> </ol> <p>The <strong>OAuth2 authentication plugin</strong> requires the <a href="https://github.com/Sainsburys/guzzle-oauth2-plugin">sainsburys/guzzle-oauth2-plugin</a> composer package to work. It provides the following configuration keys:</p> <ol> <li><strong>base_uri</strong>: A string value.</li> <li><strong>grant_type</strong>: A string value. Possible values are authorization_code, client_credentials, urn:ietf:params:oauth:grant-type:jwt-bearer, password, and refresh_token. Each of these might require extra configuration values.</li> </ol> <p>The <strong>client credentials grant type</strong> requires the following configuration keys:</p> <ol> <li><strong>token_url</strong>: A string value.</li> <li><strong>client_id</strong>: A string value.</li> <li><strong>client_secret</strong>: A string value.</li> </ol> <p>For configuration keys required by other grant types, refer to the <a href="https://github.com/Sainsburys/guzzle-oauth2-plugin/tree/master/src/GrantType">classes that implement them</a>. Read this <a href="https://agaric.coop/blog/adding-http-request-headers-and-authentication-remote-json-and-xml-drupal-migrations">article on adding HTTP request headers and authentication parameters</a> for example configurations.</p> <p>There are many combinations possible to configure this plugin. In the <a href="/31-days-of-migrations">31 days of migration series</a> there are many example configurations. For reference, below is the source plugin configuration used in the <a href="https://agaric.coop/blog/migrating-json-files-drupal#migrating-nodes">local JSON node migration example</a>:</p> <p><code>source: plugin: url data_fetcher_plugin: file data_parser_plugin: json urls: - modules/custom/ud_migrations/ud_migrations_json_source/sources/udm_data.json item_selector: /data/udm_people fields: - name: src_unique_id label: 'Unique ID' selector: unique_id - name: src_name label: 'Name' selector: name - name: src_photo_file label: 'Photo ID' selector: photo_file - name: src_book_ref label: 'Book paragraph ID' selector: book_ref ids: src_unique_id: type: integer</code></p> <h2 id="embedded_data">EmbeddedDataSource</h2> <p><strong>Module</strong>: Migrate (Drupal Core). <strong>Plugin ID</strong>: embedded_data<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21EmbeddedDataSource.php/class/EmbeddedDataSource/9.0.x">Drupal\migrate\Plugin\migrate\source\EmbeddedDataSource</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This plugin allows the definition of data to be imported right inside the migration definition file. We used this plugin in many of the examples of the <a href="/31-days-of-migrations">31 days of migration series</a>. It is also used in many core tests for the Migrate API itself.</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>data_rows</strong>: An array of all the records to be migrated. Each record might contain an arbitrary number of key-value pairs representing "columns" of data to be imported.</li> <li><strong>ids</strong>: An associative array. It contains the "columns" that uniquely identify each record. The keys are column names as defined in the data_rows key. The values is an array with a single member with key 'type' and value a column type such as 'integer'.</li> </ol> <p>Many examples of <a href="/31-days-of-migrations">31 days of migration series</a> use this plugin. You can get the example modules from <a href="https://github.com/dinarcon/ud_migrations">this repository</a>. For reference, below is the source plugin configuration used in the <a href="https://agaric.coop/blog/writing-your-first-drupal-migration">first migration example</a>:</p> <p><code>source: plugin: embedded_data data_rows: - unique_id: 1 creative_title: 'The versatility of Drupal fields' engaging_content: 'Fields are Drupal''s atomic data storage mechanism...' - unique_id: 2 creative_title: 'What is a view in Drupal? How do they work?' engaging_content: 'In Drupal, a view is a listing of information. It can a list of nodes, users, comments, taxonomy terms, files, etc...' ids: unique_id: type: integer</code></p> <p>This plugin can also be used to create default content when the data is known in advance. We often present Drupal site building workshops. To save time, we use this plugin to create nodes which are later used when explaining how to create Views. Check this <a href="https://github.com/dinarcon/wdc_emprendimiento_parcial">repository</a> for an example of this. Note that it uses a different directory structure to store the migrations as explained in this <a href="https://agaric.coop/blog/defining-drupal-migrations-configuration-entities-migrate-plus-module">blog post</a>.</p> <h2 id="content_entity">ContentEntity</h2> <p><strong>Module</strong>: Migrate Drupal (Drupal Core). <strong>Plugin ID</strong>: content_entity<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21ContentEntity.php/class/ContentEntity/9.0.x">Drupal\migrate_drupal\Plugin\migrate\source\ContentEntity</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This plugin returns content entities from a Drupal 8 or 9 installation. It uses the Entity API to get the data to migrate. If the source entity type has custom field storage fields or computed fields, this class will need to be extended and the new class will need to load/calculate the values for those fields.</p> <p>In addition to the keys provided in the parent class chain, this plugin provides the following configuration key:</p> <ol> <li><strong>entity_type</strong>: A string value. The entity type ID of the entities being migrated. This is calculated dynamically by the deriver so it is only needed if the deriver is not utilized, i.e., a custom source plugin.</li> <li><strong>bundle</strong>: An optionals string value. If set and the entity type is bundleable, only return entities of this bundle.</li> <li><strong>include_translations</strong>: An optional boolean value. If set, entity translations are included in the returned data. It defaults to TRUE.</li> </ol> <p>For reference, this is how this plugin is configured to get all nodes of type article in their default language only:</p> <p><code>source: plugin: content_entity:node bundle: article include_translations: false</code></p> <p>Note: this plugin was brought into core in <a href="https://www.drupal.org/project/drupal/issues/2935951">this issue</a> copied from the <a href="https://www.drupal.org/project/migrate_drupal_d8">Drupal 8 migration (source) module</a>. The latter can be used if the source database does not use the default connection.</p> <h2 id="table">Table</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/migrate_plus">Migrate Plus</a>. <strong>Plugin ID</strong>: table<br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate/source/Table.php">Drupal\migrate_plus\Plugin\migrate\source\Table</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SqlBase.php/class/SqlBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SqlBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, source_module, key, target, database_state_key, batch_size, and ignore_map.</p> <p>This plugin allows reading data from a single database table. It uses one of the database connections for the site as defined by the options. <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/tests/src/Kernel/MigrateTableTest.php">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 plugin provides the following configuration key:</p> <ol> <li><strong>table_name</strong>: An string value. The table to read values from.</li> <li><strong>id_fields</strong>: An associative array value. IDMap compatible array of fields that uniquely identify each record.</li> <li><strong>fields</strong>: An array value. The elements are fields ("columns") present on the source table.</li> </ol> <h2 id="empty">EmptySource (migrate module)</h2> <p><strong>Module</strong>: Migrate (Drupal Core). <strong>Plugin ID</strong>: empty<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21EmptySource.php/class/EmptySource/9.0.x">Drupal\migrate\Plugin\migrate\source\EmptySource</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/9.0.x">Drupal\migrate\Plugin\migrate\source\SourcePluginBase</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>This plugin returns an empty row by default. It can be used as a placeholder to defer setting the source plugin to a deriver. An example of this can be seen in the migrations for <a href="https://api.drupal.org/api/drupal/core%21modules%21content_translation%21migrations%21d6_entity_reference_translation.yml/9.0.x">Drupal 6</a> and <a href="https://api.drupal.org/api/drupal/core%21modules%21content_translation%21migrations%21d7_entity_reference_translation.yml/9.0.x">Drupal 7 entity reference translations</a>. In both cases, the source plugin will be determined by the <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21EntityReferenceTranslationDeriver.php/class/EntityReferenceTranslationDeriver/9.0.x">EntityReferenceTranslationDeriver</a>.</p> <p>In addition to the keys provided in the parent class chain, this plugin does not define extra configuration keys. If the plugin is used with <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#source-constants">source constants</a>, a single row containing the constant values will be returned. For example:</p> <p><code>source: plugin: empty constants: entity_type: node field_name: body</code></p> <p>The plugin will return a single row containing 'entity_type' and 'field_name' elements, with values of 'node' and 'body', respectively. This is not very useful. For the most part, the plugin is used to defer the definition to a deriver as mentioned before.</p> <h2 id="md_empty">EmptySource (migrate_drupal module)</h2> <p><strong>Module</strong>: Migrate Drupal (Drupal Core). <strong>Plugin ID</strong>: md_empty<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21source%21EmptySource.php/class/EmptySource/9.0.x">Drupal\migrate_drupal\Plugin\migrate\source\EmptySource</a><br /> <strong>Extends</strong>: <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21EmptySource.php/class/EmptySource/9.0.x">Drupal\migrate\Plugin\migrate\source\EmptySource</a><br /> <strong>Inherited configuration options</strong>: skip_count, cache_counts, cache_key, track_changes, high_water_property, and source_module.</p> <p>By default, this plugin returns an empty row with Drupal specific config dependencies. If the plugin is used with <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#source-constants">source constants</a>, a single row containing the constant values will be returned. These can be seen in the <a href="https://api.drupal.org/api/drupal/core%21modules%21user%21migrations%21user_picture_field.yml/9.0.x">user_picture_field.yml</a> and <a href="https://api.drupal.org/api/drupal/core%21modules%21file%21migrations%21d6_upload_field.yml/9.0.x">d6_upload_field.yml</a> migrations.</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>constants</strong>: An optional array value used to add module dependencies. The value is an associative array with one possible element. An entity_type key is used to add a dependency on the module that provides the specified entity type.</li> </ol> <h2 id="other-plugins">Available configuration for other migrate source plugins</h2> <p>In Drupal core itself there are more than 100 migrate source plugins, most of which come from the Migrate Drupal module. 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="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21MigrateSourcePluginManager.php/class/MigrateSourcePluginManager/9.0.x">plugin.manager.migrate.source</a> service and call its getFieldStorageDefinitions() method. This will return all migrate source 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 source plugin definitions. $ drush php:eval "print_r(\Drupal::service('plugin.manager.migrate.source')-&gt;getDefinitions());" # List of migrate source plugin ids. $ drush php:eval "print_r(array_keys(\Drupal::service('plugin.manager.migrate.source')-&gt;getDefinitions()));"</code></p> <p>To find out which configuration options are available for any source 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\source namespace. The class itself would be in a file under the /src/Plugin/migrate/source/ directory of the module.</li> <li>Look up in the class hierarchy. The source 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 offers some extension mechanism that would allow it to use other types of plugins. For example, the data fetcher, data parses, and authentication plugins provided by Migrate Plus. The Url migrate source plugin does this.</li> </ul> <p>What did you learn in today’s article? Did you know that migrate source plugins can inherit configuration keys from their class hierarchy? Were you aware that there are so many source plugins? Other than the ones listed here, which source 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 13 Mauricio Dinarte https://agaric.coop/Drupal%20migrations%20reference%3A%20List%20of%20configuration%20options%20for%20source%20plugins Drupal migrations reference: List of properties per content entity https://agaric.coop/ <div class="flow_middle"> <p>In a previous article we explained the <a href="https://agaric.coop/blog/understanding-syntax-drupal-migrations">syntax used to write Drupal migrations</a>. When migrating into content entities, these define several properties that can be included in the process section to populate their values. For example, when importing nodes you can specify the title, publication status, creation date, etc. In the case of users, you can set the username, password, timezone, etc. Finding out which properties are available for an entity might require some Drupal development knowledge. To make the process easier, in today’s article we are presenting a reference of properties available in content entities provided by Drupal core and some contributed modules.</p> <p><img alt="Example migrations mapping of content entity properties" data-entity-type="file" data-entity-uuid="e61f4c86-fbed-40fb-ac5c-ab72ffd6456a" src="/sites/default/files/inline-images/migrations-properties-per-content-entity-list.jpg" width="960" height="540" /></p> <p>For each entity we will present: the module that provides it, the class that defines it, and the available properties. For each property we will list its name, field type, a description, and a note if the field allows unlimited values (i.e. it has an unlimited cardinality). The list of properties available for a content entity depend on many factors. For example, if the entity is revisionable (e.g. revision_default), translatable (e.g. langcode), or both (e.g. revision_translation_affected). The modules that are enabled on the site can also affect the available properties. For instance, if the “Workspaces” module is installed, it will add a workspace property to many content entities. This reference assumes that Drupal was installed using the standard installation profile and all modules that provide content entities are enabled.</p> <p>It is worth noting that entity properties are divided in two categories: base field definitions and field storage configurations. Base field configurations will always be available for the entity. On the other hand, the presence of field storage configurations will depend on various factors. For one, they can only be added to fieldable entities. Attaching the fields to the entity can be done manually by the user, by a module, or by an installation profile. Again, this reference assumes that Drupal was installed using the standard installation profile. Among other things, it adds a user_picture image field to the user entity and body, comment, field_image, and field_tags fields to the node entity. For entities that can have multiple bundles, not all properties provided by the field storage configurations will be available in all bundles. For example, with the standard installation profile all content types will have a body field associated with it, but only the article content type has the field_image, and field_tags fields. If <a href="https://agaric.coop/blog/drupal-migrations-reference-list-subfields-field-type">subfields are available for the field type</a>, you can migrate into them.</p> <h2 id="node">Content (Node) entity</h2> <p><strong>Module</strong>: Node (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!node!src!Entity!Node.php/class/Node/9.0.x">Drupal\node\Entity\Node</a><br /> Related article: <a href="https://agaric.coop/blog/writing-your-first-drupal-migration">Writing your first Drupal migration</a></p> <p>List of base field definitions:</p> <ol> <li><strong>nid</strong>: (integer) The node ID.</li> <li><strong>uuid</strong>: (uuid) The node UUID.</li> <li><strong>vid</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) Language code (e.g. en).</li> <li><strong>type</strong>: (entity_reference to node_type) Content type machine name.</li> <li><strong>revision_timestamp</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_uid</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log</strong>: (string_long) Briefly describe the changes you have made.</li> <li><strong>status</strong>: (boolean) Node published when set to TRUE.</li> <li><strong>uid</strong>: (entity_reference to user) The user ID of the content author.</li> <li><strong>title</strong>: (string) Title.</li> <li><strong>created</strong>: (created) The time that the node was created.</li> <li><strong>changed</strong>: (changed) The time that the node was last edited.</li> <li><strong>promote</strong>: (boolean) Node promoted to front page when set to TRUE.</li> <li><strong>sticky</strong>: (boolean) Node sticky at top of lists when set to TRUE.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <p>List of field storage configurations:</p> <ol> <li><strong>body</strong>: text_with_summary field.</li> <li><strong>comment</strong>: comment field.</li> <li><strong>field_image</strong>: image field.</li> <li><strong>field_tags</strong>: entity_reference field.</li> </ol> <h2 id="user">User entity</h2> <p><strong>Module</strong>: User (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!user!src!Entity!User.php/class/User/9.0.x">Drupal\user\Entity\User</a><br /> Related articles: <a href="https://agaric.coop/blog/migrating-users-drupal-part-1">Migrating users into Drupal - Part 1</a> and <a href="https://agaric.coop/blog/migrating-users-drupal-part-2">Migrating users into Drupal - Part 2</a></p> <p>List of base field definitions:</p> <ol> <li><strong>uid</strong>: (integer) The user ID.</li> <li><strong>uuid</strong>: (uuid) The user UUID.</li> <li><strong>langcode</strong>: (language) The user language code.</li> <li><strong>preferred_langcode</strong>: (language) The user's preferred language code for receiving emails and viewing the site.</li> <li><strong>preferred_admin_langcode</strong>: (language) The user's preferred language code for viewing administration pages.</li> <li><strong>name</strong>: (string) The name of this user.</li> <li><strong>pass</strong>: (password) The password of this user (hashed).</li> <li><strong>mail</strong>: (email) The email of this user.</li> <li><strong>timezone</strong>: (string) The timezone of this user.</li> <li><strong>status</strong>: (boolean) Whether the user is active or blocked.</li> <li><strong>created</strong>: (created) The time that the user was created.</li> <li><strong>changed</strong>: (changed) The time that the user was last edited.</li> <li><strong>access</strong>: (timestamp) The time that the user last accessed the site.</li> <li><strong>login</strong>: (timestamp) The time that the user last logged in.</li> <li><strong>init</strong>: (email) The email address used for initial account creation.</li> <li><strong>roles</strong>: (entity_reference to user_role) The roles the user has. Allows unlimited values.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> </ol> <p>List of field storage configurations:</p> <ol> <li><strong>user_picture</strong>: image field.</li> </ol> <h2 id="taxonomy_term">Taxonomy term entity</h2> <p><strong>Module</strong>: Taxonomy (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!taxonomy!src!Entity!Term.php/class/Term/9.0.x">Drupal\taxonomy\Entity\Term</a><br /> Related article: <a href="https://agaric.coop/blog/migrating-taxonomy-terms-and-multivalue-fields-drupal">Migrating taxonomy terms and multivalue fields into Drupal</a></p> <p>List of base field definitions:</p> <ol> <li><strong>tid</strong>: (integer) The term ID.</li> <li><strong>uuid</strong>: (uuid) The term UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) The term language code.</li> <li><strong>vid</strong>: (entity_reference to taxonomy_vocabulary) The vocabulary to which the term is assigned.</li> <li><strong>revision_created</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_user</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log_message</strong>: (string_long) Briefly describe the changes you have made.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>name</strong>: (string) Name.</li> <li><strong>description</strong>: (text_long) Description.</li> <li><strong>weight</strong>: (integer) The weight of this term in relation to other terms.</li> <li><strong>parent</strong>: (entity_reference to taxonomy_term) The parents of this term. Allows unlimited values.</li> <li><strong>changed</strong>: (changed) The time that the term was last edited.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <h2 id="file">File entity</h2> <p><strong>Module</strong>: File (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!file!src!Entity!File.php/class/File/9.0.x">Drupal\file\Entity\File</a><br /> Related articles: <a href="https://agaric.coop/blog/migrating-files-and-images-drupal">Migrating files and images into Drupal</a>, <a href="https://agaric.coop/blog/migrating-data-drupal-subfields#images">Migrating images using the image_import plugin</a>, and <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#migrating-images">Migrating images using the image_import plugin</a></p> <p>List of base field definitions:</p> <ol> <li><strong>fid</strong>: (integer) The file ID.</li> <li><strong>uuid</strong>: (uuid) The file UUID.</li> <li><strong>langcode</strong>: (language) The file language code.</li> <li><strong>uid</strong>: (entity_reference to user) The user ID of the file.</li> <li><strong>filename</strong>: (string) Name of the file with no path components.</li> <li><strong>uri</strong>: (file_uri) The URI to access the file (either local or remote).</li> <li><strong>filemime</strong>: (string) The file's MIME type.</li> <li><strong>filesize</strong>: (integer) The size of the file in bytes.</li> <li><strong>status</strong>: (boolean) The status of the file, temporary (FALSE) and permanent (TRUE).</li> <li><strong>created</strong>: (created) The timestamp that the file was created.</li> <li><strong>changed</strong>: (changed) The timestamp that the file was last changed.</li> </ol> <h2 id="media">Media entity</h2> <p><strong>Module</strong>: Media (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!media!src!Entity!Media.php/class/Media/9.0.x">Drupal\media\Entity\Media</a></p> <p>List of base field definitions:</p> <ol> <li><strong>mid</strong>: (integer) The media ID.</li> <li><strong>uuid</strong>: (uuid) The media UUID.</li> <li><strong>vid</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) Language code (e.g. en).</li> <li><strong>bundle</strong>: (entity_reference to media_type) Media type.</li> <li><strong>revision_created</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_user</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log_message</strong>: (string_long) Briefly describe the changes you have made.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>uid</strong>: (entity_reference to user) The user ID of the author.</li> <li><strong>name</strong>: (string) Name.</li> <li><strong>thumbnail</strong>: (image) The thumbnail of the media item.</li> <li><strong>created</strong>: (created) The time the media item was created.</li> <li><strong>changed</strong>: (changed) The time the media item was last edited.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <p>List of field storage configurations:</p> <ol> <li><strong>field_media_audio_file</strong>: file field.</li> <li><strong>field_media_document</strong>: file field.</li> <li><strong>field_media_image</strong>: image field.</li> <li><strong>field_media_oembed_video</strong>: string field.</li> <li><strong>field_media_video_file</strong>: file field.</li> </ol> <h2 id="comment">Comment entity</h2> <p><strong>Module</strong>: Comment (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!comment!src!Entity!Comment.php/class/Comment/9.0.x">Drupal\comment\Entity\Comment</a></p> <p>List of base field definitions:</p> <ol> <li><strong>cid</strong>: (integer) The comment ID.</li> <li><strong>uuid</strong>: (uuid) The comment UUID.</li> <li><strong>langcode</strong>: (language) The comment language code.</li> <li><strong>comment_type</strong>: (entity_reference to comment_type) The comment type.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>uid</strong>: (entity_reference to user) The user ID of the comment author.</li> <li><strong>pid</strong>: (entity_reference to comment) The parent comment ID if this is a reply to a comment.</li> <li><strong>entity_id</strong>: (entity_reference to node) The ID of the entity of which this comment is a reply.</li> <li><strong>subject</strong>: (string) Subject.</li> <li><strong>name</strong>: (string) The comment author's name.</li> <li><strong>mail</strong>: (email) The comment author's email address.</li> <li><strong>homepage</strong>: (uri) The comment author's home page address.</li> <li><strong>hostname</strong>: (string) The comment author's hostname.</li> <li><strong>created</strong>: (created) The time that the comment was created.</li> <li><strong>changed</strong>: (changed) The time that the comment was last edited.</li> <li><strong>thread</strong>: (string) The alphadecimal representation of the comment's place in a thread, consisting of a base 36 string prefixed by an integer indicating its length.</li> <li><strong>entity_type</strong>: (string) The entity type to which this comment is attached.</li> <li><strong>field_name</strong>: (string) The field name through which this comment was added.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> </ol> <p>List of field storage configurations:</p> <ol> <li><strong>comment_body</strong>: text_long field.</li> </ol> <h2 id="aggregator_feed">Aggregator feed entity</h2> <p><strong>Module</strong>: Aggregator (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!aggregator!src!Entity!Feed.php/class/Feed/9.0.x">Drupal\aggregator\Entity\Feed</a></p> <p>List of base field definitions:</p> <ol> <li><strong>fid</strong>: (integer) The ID of the aggregator feed.</li> <li><strong>uuid</strong>: (uuid) The aggregator feed UUID.</li> <li><strong>langcode</strong>: (language) The feed language code.</li> <li><strong>title</strong>: (string) The name of the feed (or the name of the website providing the feed).</li> <li><strong>url</strong>: (uri) The fully-qualified URL of the feed.</li> <li><strong>refresh</strong>: (list_integer) The length of time between feed updates. Requires a correctly configured cron maintenance task.</li> <li><strong>checked</strong>: (timestamp) Last time feed was checked for new items, as Unix timestamp.</li> <li><strong>queued</strong>: (timestamp) Time when this feed was queued for refresh, 0 if not queued.</li> <li><strong>link</strong>: (uri) The link of the feed.</li> <li><strong>description</strong>: (string_long) The parent website's description that comes from the &lt;description&gt; element in the feed.</li> <li><strong>image</strong>: (uri) An image representing the feed.</li> <li><strong>hash</strong>: (string) Calculated hash of the feed data, used for validating cache.</li> <li><strong>etag</strong>: (string) Entity tag HTTP response header, used for validating cache.</li> <li><strong>modified</strong>: (timestamp) When the feed was last modified, as a Unix timestamp.</li> </ol> <h2 id="aggregator_item">Aggregator feed item entity</h2> <p><strong>Module</strong>: Aggregator (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!aggregator!src!Entity!Item.php/class/Item/9.0.x">Drupal\aggregator\Entity\Item</a></p> <p>List of base field definitions:</p> <ol> <li><strong>iid</strong>: (integer) The ID of the feed item.</li> <li><strong>langcode</strong>: (language) The feed item language code.</li> <li><strong>fid</strong>: (entity_reference to aggregator_feed) The aggregator feed entity associated with this item.</li> <li><strong>title</strong>: (string) The title of the feed item.</li> <li><strong>link</strong>: (uri) The link of the feed item.</li> <li><strong>author</strong>: (string) The author of the feed item.</li> <li><strong>description</strong>: (string_long) The body of the feed item.</li> <li><strong>timestamp</strong>: (created) Posted date of the feed item, as a Unix timestamp.</li> <li><strong>guid</strong>: (string_long) Unique identifier for the feed item.</li> </ol> <h2 id="block_content">Custom block entity</h2> <p><strong>Module</strong>: Custom Block (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!block_content!src!Entity!BlockContent.php/class/BlockContent/9.0.x">Drupal\block_content\Entity\BlockContent</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) The custom block ID.</li> <li><strong>uuid</strong>: (uuid) The custom block UUID.</li> <li><strong>revision_id</strong>: (integer) The revision ID.</li> <li><strong>langcode</strong>: (language) The custom block language code.</li> <li><strong>type</strong>: (entity_reference to block_content_type) The block type.</li> <li><strong>revision_created</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_user</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log</strong>: (string_long) The log entry explaining the changes in this revision.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>info</strong>: (string) A brief description of your block.</li> <li><strong>changed</strong>: (changed) The time that the custom block was last edited.</li> <li><strong>reusable</strong>: (boolean) A boolean indicating whether this block is reusable.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <p>List of field storage configurations:</p> <ol> <li><strong>body</strong>: text_with_summary field.</li> </ol> <h2 id="contact_message">Contact message entity</h2> <p><strong>Module</strong>: Contact (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!contact!src!Entity!Message.php/class/Message/9.0.x">Drupal\contact\Entity\Message</a></p> <p>List of base field definitions:</p> <ol> <li><strong>uuid</strong>: (uuid) The message UUID.</li> <li><strong>langcode</strong>: (language) The message language code.</li> <li><strong>contact_form</strong>: (entity_reference to contact_form) The ID of the associated form.</li> <li><strong>name</strong>: (string) The name of the person that is sending the contact message.</li> <li><strong>mail</strong>: (email) The email of the person that is sending the contact message.</li> <li><strong>subject</strong>: (string) Subject.</li> <li><strong>message</strong>: (string_long) Message.</li> <li><strong>copy</strong>: (boolean) Whether to send a copy of the message to the sender.</li> <li><strong>recipient</strong>: (entity_reference to user) The ID of the recipient user for personal contact messages.</li> </ol> <h2 id="content_moderation_state">Content moderation state entity</h2> <p><strong>Module</strong>: Content Moderation (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!content_moderation!src!Entity!ContentModerationState.php/class/ContentModerationState/9.0.x">Drupal\content_moderation\Entity\ContentModerationState</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) ID.</li> <li><strong>uuid</strong>: (uuid) UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) Language.</li> <li><strong>uid</strong>: (entity_reference to user) The username of the entity creator.</li> <li><strong>workflow</strong>: (entity_reference to workflow) The workflow the moderation state is in.</li> <li><strong>moderation_state</strong>: (string) The moderation state of the referenced content.</li> <li><strong>content_entity_type_id</strong>: (string) The ID of the content entity type this moderation state is for.</li> <li><strong>content_entity_id</strong>: (integer) The ID of the content entity this moderation state is for.</li> <li><strong>content_entity_revision_id</strong>: (integer) The revision ID of the content entity this moderation state is for.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> </ol> <h2 id="path_alias">URL alias entity</h2> <p><strong>Module</strong>: Path alias (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!path_alias!src!Entity!PathAlias.php/class/PathAlias/9.0.x">Drupal\path_alias\Entity\PathAlias</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) ID.</li> <li><strong>uuid</strong>: (uuid) UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) Language.</li> <li><strong>path</strong>: (string) The path that this alias belongs to.</li> <li><strong>alias</strong>: (string) An alias used with this path.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <h2 id="shortcut">Shortcut link entity</h2> <p><strong>Module</strong>: Shortcut (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!shortcut!src!Entity!Shortcut.php/class/Shortcut/9.0.x">Drupal\shortcut\Entity\Shortcut</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) The ID of the shortcut.</li> <li><strong>uuid</strong>: (uuid) The UUID of the shortcut.</li> <li><strong>langcode</strong>: (language) The language code of the shortcut.</li> <li><strong>shortcut_set</strong>: (entity_reference to shortcut_set) The bundle of the shortcut.</li> <li><strong>title</strong>: (string) The name of the shortcut.</li> <li><strong>weight</strong>: (integer) Weight among shortcuts in the same shortcut set.</li> <li><strong>link</strong>: (link) The location this shortcut points to.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> </ol> <h2 id="workspace">Workspace entity</h2> <p><strong>Module</strong>: Workspaces (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!workspaces!src!Entity!Workspace.php/class/Workspace/9.0.x">Drupal\workspaces\Entity\Workspace</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (string) The workspace ID.</li> <li><strong>uuid</strong>: (uuid) UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>uid</strong>: (entity_reference to user) The workspace owner.</li> <li><strong>label</strong>: (string) The workspace name.</li> <li><strong>parent</strong>: (entity_reference to workspace) The parent workspace.</li> <li><strong>changed</strong>: (changed) The time that the workspace was last edited.</li> <li><strong>created</strong>: (created) The time that the workspace was created.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> </ol> <h2 id="menu_link_content">Custom menu link entity</h2> <p><strong>Module</strong>: Custom Menu Links (Drupal Core)<br /> <strong>Class</strong>: <a href="https://api.drupal.org/api/drupal/core!modules!menu_link_content!src!Entity!MenuLinkContent.php/class/MenuLinkContent/9.0.x">Drupal\menu_link_content\Entity\MenuLinkContent</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) The entity ID for this menu link content entity.</li> <li><strong>uuid</strong>: (uuid) The content menu link UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) The menu link language code.</li> <li><strong>bundle</strong>: (string) The content menu link bundle.</li> <li><strong>revision_created</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_user</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log_message</strong>: (string_long) Briefly describe the changes you have made.</li> <li><strong>enabled</strong>: (boolean) A flag for whether the link should be enabled in menus or hidden.</li> <li><strong>title</strong>: (string) The text to be used for this link in the menu.</li> <li><strong>description</strong>: (string) Shown when hovering over the menu link.</li> <li><strong>menu_name</strong>: (string) The menu name. All links with the same menu name (such as "tools") are part of the same menu.</li> <li><strong>link</strong>: (link) The location this menu link points to.</li> <li><strong>external</strong>: (boolean) A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).</li> <li><strong>rediscover</strong>: (boolean) Indicates whether the menu link should be rediscovered.</li> <li><strong>weight</strong>: (integer) Link weight among links in the same menu at the same depth. In the menu, the links with high weight will sink and links with a low weight will be positioned nearer the top.</li> <li><strong>expanded</strong>: (boolean) If selected and this menu link has children, the menu will always appear expanded. This option may be overridden for the entire menu tree when placing a menu block.</li> <li><strong>parent</strong>: (string) The ID of the parent menu link plugin, or empty string when at the top level of the hierarchy.</li> <li><strong>changed</strong>: (changed) The time that the menu link was last edited.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <h2 id="paragraph">Paragraph entity</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/paragraphs">Paragraphs module</a><br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/paragraphs/-/blob/8.x-1.x/src/Entity/Paragraph.php">Drupal\paragraphs\Entity\Paragraph</a><br /> Related article: <a href="https://agaric.coop/blog/introduction-paragraphs-migrations-drupal">Introduction to paragraphs migrations in Drupal</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) ID.</li> <li><strong>uuid</strong>: (uuid) UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) The paragraphs entity language code.</li> <li><strong>type</strong>: (entity_reference to paragraphs_type) Paragraph type.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>created</strong>: (created) The time that the Paragraph was created.</li> <li><strong>parent_id</strong>: (string) The ID of the parent entity of which this entity is referenced.</li> <li><strong>parent_type</strong>: (string) The entity parent type to which this entity is referenced.</li> <li><strong>parent_field_name</strong>: (string) The entity parent field name to which this entity is referenced.</li> <li><strong>behavior_settings</strong>: (string_long) The behavior plugin settings</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <p>List of field storage configurations:</p> <ol> <li><strong>field_reusable_paragraph</strong>: entity_reference field.</li> </ol> <h2 id="paragraphs_library_item">Paragraphs library item entity</h2> <p><strong>Module</strong>: Paragraphs Library (part of <a href="https://www.drupal.org/project/paragraphs">paragraphs module</a>)<br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/paragraphs/-/blob/8.x-1.x/modules/paragraphs_library/src/Entity/LibraryItem.php">Drupal\paragraphs_library\Entity\LibraryItem</a></p> <p>List of base field definitions:</p> <ol> <li><strong>id</strong>: (integer) ID.</li> <li><strong>uuid</strong>: (uuid) UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>langcode</strong>: (language) Language.</li> <li><strong>revision_created</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_uid</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log</strong>: (string_long) Briefly describe the changes you have made.</li> <li><strong>status</strong>: (boolean) Published.</li> <li><strong>label</strong>: (string) Label.</li> <li><strong>paragraphs</strong>: (entity_reference_revisions) Paragraphs.</li> <li><strong>created</strong>: (created) The time that the library item was created.</li> <li><strong>changed</strong>: (changed) The time that the library item was last edited.</li> <li><strong>uid</strong>: (entity_reference to user) The user ID of the library item author.</li> <li><strong>default_langcode</strong>: (boolean) A flag indicating whether this is the default translation.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>revision_translation_affected</strong>: (boolean) Indicates if the last edit of a translation belongs to current revision.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <h2 id="profile">Profile entity</h2> <p><strong>Module</strong>: <a href="https://www.drupal.org/project/profile">Profile module</a><br /> <strong>Class</strong>: <a href="https://git.drupalcode.org/project/profile/-/blob/8.x-1.x/src/Entity/Profile.php">Drupal\profile\Entity\Profile</a></p> <p>List of base field definitions:</p> <ol> <li><strong>profile_id</strong>: (integer) ID.</li> <li><strong>uuid</strong>: (uuid) UUID.</li> <li><strong>revision_id</strong>: (integer) Revision ID.</li> <li><strong>type</strong>: (entity_reference to profile_type) Profile type.</li> <li><strong>revision_created</strong>: (created) The time that the current revision was created.</li> <li><strong>revision_user</strong>: (entity_reference to user) The user ID of the author of the current revision.</li> <li><strong>revision_log_message</strong>: (string_long) Briefly describe the changes you have made.</li> <li><strong>status</strong>: (boolean) Whether the profile is active.</li> <li><strong>uid</strong>: (entity_reference to user) The user that owns this profile.</li> <li><strong>is_default</strong>: (boolean) Whether this is the default profile.</li> <li><strong>data</strong>: (map) A serialized array of additional data.</li> <li><strong>created</strong>: (created) The time when the profile was created.</li> <li><strong>changed</strong>: (changed) The time when the profile was last edited.</li> <li><strong>revision_default</strong>: (boolean) A flag indicating whether this was a default revision when it was saved.</li> <li><strong>workspace</strong>: (entity_reference to workspace) Indicates the workspace that this revision belongs to.</li> </ol> <h2 id="contrib">Available properties for other content entities</h2> <p>This reference includes all core content entities and some provided by contributed modules. The next article will include a <a href="https://agaric.coop/blog/drupal-migrations-reference-list-properties-commerce-content-entity">reference for Drupal Commerce content entities</a>. That being said, it would be impractical to cover all contributed modules. To get a list of yourself for other content entities, load the <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/lib/Drupal/Core/Entity/EntityFieldManager.php">entity_type.manager</a> service and call its <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/lib/Drupal/Core/Entity/EntityFieldManager.php#L430">getFieldStorageDefinitions()</a> method passing the machine name of the entity as a parameter. Although this reference only covers content entities, the same process can be used for configuration entities.</p> <p>What did you learn in today’s article? Did you know that there were so many entity properties in Drupal core? Were you aware that the list of available properties depend on factors like if the entity is fieldable, translatable, and revisionable? Did you know how to find properties for content entities from contributed modules? 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 08 Mauricio Dinarte https://agaric.coop/Drupal%20migrations%20reference%3A%20List%20of%20properties%20per%20content%20entity Drupal migrations reference: List of configuration options in YAML definition files https://agaric.coop/ <div class="flow_middle"> <p>In today’s article we are going to provide a reference of all <strong>configuration options</strong> that can be set in migration definition files. Additional configuration options available for <strong>migrations defined as configuration</strong> will also be listed. Finally, we present the configuration options for <strong>migrations groups</strong>.</p> <p><img alt="List of configuration options in YAML definition files" data-entity-type="file" data-entity-uuid="1817e521-8776-423d-acbe-0a80f75d1134" src="/sites/default/files/inline-images/migrations-list-configuration-options-in-yaml-definition-files.jpg" width="960" height="540" loading="lazy" /></p> <h2 id="general">General configuration keys</h2> <p>The following keys can be set for any Drupal migration.</p> <h3 id="id">id key</h3> <p>A required string value. It serves as the identifier for the migration. The value should be a machine name. That is, lowercase letters with underscores instead of spaces. The value is for creating mapping and messages tables. For example, if the id is ud_migrations, the Migrate API will create the following migrations migrate_map_ud_migrations and migrate_message_ud_migrations.</p> <h3 id="label">label key</h3> <p>A string value. The human-readable label for the migration. The value is used in different interfaces to refer to the migration.</p> <h3 id="audit">audit key</h3> <p>A boolean value. Defaults to FALSE. It indicates whether the migration is auditable. When set to TRUE, a warning is displayed if entities might be overridden when the migration is executed. For example, when doing an upgrade from a previous version of Drupal, nodes created in the new site before running the automatic upgrade process would be overridden and a warning is logged. The Migrate API checks if the highest destination ID is greater than the highest source ID.</p> <h3 id="migration_tags">migration_tags key</h3> <p>An array value. It can be set to an optional list of strings representing the tags associated with the migration. They are used by the plugin manager for filtering. For example, you can import or rollback all migrations with the Content tag using the following Drush commands provided by the Migrate Tools module:</p> <pre> <code class="language-yaml">$ drush migrate:import --tag='Content' $ drush migrate:rollback --tag='Content'</code></pre><h3 id="source">source key</h3> <p>A nested array value. This represents the configuration of the source plugin. At a minimum, it contains an id key which indicates which source plugin to use for the migration. Possible values include embedded_data for <a href="https://agaric.coop/blog/writing-your-first-drupal-migration">hardcoded data</a>; csv for <a href="https://agaric.coop/blog/migrating-csv-files-drupal">CSV files</a>; url for <a href="https://agaric.coop/blog/migrating-json-files-drupal">JSON feeds</a>, <a href="https://agaric.coop/blog/migrating-xml-files-drupal">XML files</a>, and <a href="https://agaric.coop/blog/migrating-google-sheets-drupal">Google Sheets</a>; spreadsheet for <a href="https://agaric.coop/blog/migrating-microsoft-excel-and-libreoffice-calc-files-drupal">Microsoft Excel and LibreOffice Calc files</a>; and many more. Each plugin is configured differently. Refer to our <a href="https://understanddrupal.com/articles/drupal-migrations-reference-list-configuration-options-source-plugins">list of configuration options for source plugins</a> to find out what is available for each of them. Additionally, in this section you can define <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#source-constants">source contents</a> that can be later used in the <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#process-pipeline">process pipeline</a>.</p> <h3 id="process">process key</h3> <p>A nested array value. This represents the configuration of how source data will be processed and transformed to match the expected destination structure. This section contains a list of entity properties (e.g. nid for a node) and fields (e.g. field_image in the default article content type). Refer to our <a href="https://understanddrupal.com/articles/drupal-migrations-reference-list-properties-content-entity">list of properties for content entities</a> including <a href="https://agaric.coop/blog/drupal-migrations-reference-list-properties-commerce-content-entity">Commerce related entities</a> to find out which properties can be set depending on your destination (e.g. <a href="https://agaric.coop/blog/writing-your-first-drupal-migration">nodes</a>, <a href="https://agaric.coop/blog/migrating-users-drupal-part-1">users</a>, <a href="https://agaric.coop/blog/migrating-taxonomy-terms-and-multivalue-fields-drupal">taxonomy terms</a>, <a href="https://agaric.coop/blog/migrating-files-and-images-drupal">files and images</a>, <a href="https://agaric.coop/blog/introduction-paragraphs-migrations-drupal">paragraphs</a>, etc.). For field mappings, you use the machine name of the field as configured in the entity bundle. Some fields have complex structures so you migrate data into specific <a href="https://agaric.coop/blog/migrating-data-drupal-subfields">subfields</a>. Refer to our <a href="https://agaric.coop/blog/drupal-migrations-reference-list-subfields-field-type">list of subfields per field type</a> to determine which options are available. When migrating multivalue fields, you might need to set <a href="https://agaric.coop/blog/understanding-syntax-drupal-migrations#deltas">deltas</a> as well. Additionally, you can have <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#pseudofields">pseudofields</a> to store temporary values within the <a href="https://agaric.coop/blog/using-constants-and-pseudofields-data-placeholders-drupal-migration-process-pipeline#process-pipeline">process pipeline</a>.</p> <p>For each entity property, field, or pseudofield, you can use one or more <a href="https://agaric.coop/blog/using-process-plugins-data-transformation-drupal-migrations">process plugins</a> to manipulate the data. Many of them are <a href="https://www.drupal.org/docs/8/api/migrate-api/migrate-process-plugins/list-of-core-migrate-process-plugins">provided by Drupal core</a> while others become available when contributed modules are installed on the site like <a href="https://www.drupal.org/docs/8/api/migrate-api/migrate-process-plugins/list-of-process-plugins-provided-by-migrate-plus">Migrate Plus</a> and <a href="https://www.drupal.org/project/migrate_process_extra">Migrate Process Extra</a>. Throughout the <a href="https://agaric.coop/31-days-drupal-migrations">31 days of migrations series</a>, we provided examples of how many process plugins are used. Most of the work for migrations will be devoted to configuring the right mappings in the process section. Make sure to check our <a href="https://agaric.coop/blog/how-debug-drupal-migrations-part-1">debugging</a> <a href="https://agaric.coop/blog/how-debug-drupal-migrations-part-2">tips</a> in case some values are not migrated properly.</p> <h3 id="destination">destination key</h3> <p>A nested array value. This represents the configuration of the destination plugin. At a minimum, it contains an id key which indicates which destination plugin to use for the migration. Possible values include entity:node for <a href="https://agaric.coop/blog/writing-your-first-drupal-migration">nodes</a>, entity:user for <a href="https://agaric.coop/blog/migrating-users-drupal-part-1">users</a>, entity:taxonomy_term for <a href="https://agaric.coop/blog/migrating-taxonomy-terms-and-multivalue-fields-drupal">taxonomy terms</a>, entity:file for <a href="https://agaric.coop/blog/migrating-files-and-images-drupal">files and images</a>, entity_reference_revisions:paragraph for <a href="https://agaric.coop/blog/introduction-paragraphs-migrations-drupal">paragraphs</a>, and many more. Each plugin is configured differently. Refer to our <a href="https://understanddrupal.com/articles/drupal-migrations-reference-list-configuration-options-destination-plugins">list of configuration options for destination plugins</a> to find out what is available for each of them.</p> <p>This is an example migration from the <a href="https://github.com/dinarcon/ud_migrations/tree/master/ud_migrations_csv_source">ud_migrations_csv_source module</a> used in the article on <a href="https://agaric.coop/blog/migrating-csv-files-drupal">CSV sources</a>.</p> <pre> <code class="language-yaml">id: udm_csv_source_paragraph label: 'UD dependee paragraph migration for CSV source example' migration_tags: - UD CSV Source - UD Example source: plugin: csv path: modules/custom/ud_migrations/ud_migrations_csv_source/sources/udm_book_paragraph.csv ids: [book_id] header_offset: null fields: - name: book_id - name: book_title - name: 'Book author' process: field_ud_book_paragraph_title: book_title field_ud_book_paragraph_author: 'Book author' destination: plugin: 'entity_reference_revisions:paragraph' default_bundle: ud_book_paragraph</code></pre><h3 id="migration_dependencies">migration_dependencies key</h3> <p>A nested array value. The value is used by the Migrate API to make sure the listed migrations are executed in advance of the current one. For example, a node migration might require users to be imported first so you can specify who is the author of the node. Also, it is possible to list optional migrations so that they are only executed in case they are present. The following example from the d7_node.yml migration shows how key can be configured:</p> <pre> <code class="language-yaml">migration_dependencies: required: - d7_user - d7_node_type optional: - d7_field_instance - d7_comment_field_instance</code></pre><p>To configure the migration dependencies you specify required and optional subkeys whose values are an array of migration IDs. If no dependencies are needed, you can omit this key. Alternatively, you can set either required or optional dependencies without having to specify both keys. As of Drupal 8.8 an <a href="https://www.drupal.org/node/3077671">InvalidPluginDefinitionException will be thrown</a> if the migration_dependencies key is incorrectly formatted.</p> <h3 id="class">class key</h3> <p>A string value. If set, it should point to the class used as the migration plugin. The MigrationPluginManager sets this key to \Drupal\migrate\Plugin\Migration by default. Whatever class specified here should implement the MigrationInterface. This configuration key rarely needs to be set as the default value can be used most of the time. In Drupal core there are few cases where a different class is used as the migration plugin:</p> <ul> <li><a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/content_translation/migrations/d7_node_translation.yml">d7_node_translation.yml</a> sets this value to <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21D7NodeTranslation.php/class/D7NodeTranslation/9.0.x">D7NodeTranslation</a>.</li> <li><a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/content_translation/migrations/d7_user_entity_translation.yml">d7_user_entity_translation.yml</a> sets this key to <a href="https://api.drupal.org/api/drupal/core%21modules%21user%21src%21Plugin%21migrate%21User.php/9.0.x">Drupal\user\Plugin\migrate\User</a>.</li> <li><a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/content_translation/migrations/d7_comment_entity_translation.yml">d7_comment_entity_translation.yml</a> sets this key to <a href="https://api.drupal.org/api/drupal/core%21modules%21comment%21src%21Plugin%21migrate%21D7Comment.php/class/D7Comment/9.0.x">Drupal\comment\Plugin\migrate\D7Comment</a>.</li> <li><a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/field/migrations/d7_field.yml">d7_field.yml</a>, <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/field/migrations/d7_field_formatter_settings.yml">d7_field_formatter_settings.yml</a>, <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/field/migrations/d7_field_instance.yml">d7_field_instance.yml</a>, and <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/field/migrations/d7_field_instance_widget_settings.yml">d7_field_instance_widget_settings.yml</a> all set this key to. <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21FieldMigration.php/class/FieldMigration/9.0.x">Drupal\migrate_drupal\Plugin\migrate\FieldMigration</a>. There are Drupal 6 counterparts for these migrations.</li> </ul> <h3 id="deriver">deriver key</h3> <p>A string value. If set, it should point to the class used as a plugin deriver for this migration. This is an advanced topic that will be covered in a future entry. In short, it is a mechanism in which new migration plugins can be created dynamically from a base template. For example, the <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/node/migrations/d7_node.yml">d7_node.yml</a> migration uses the <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21D7NodeDeriver.php/class/D7NodeDeriver/9.0.x">D7NodeDeriver</a> to create one node migration per content type during a Drupal upgrade operation. In this case, the configuration key is set to <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21D7NodeDeriver.php/class/D7NodeDeriver/9.0.x">Drupal\node\Plugin\migrate\D7NodeDeriver</a>. There are many other derivers used by the Migrate API including <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21D7NodeDeriver.php/class/D7NodeDeriver/9.0.x">D7NodeDeriver</a>, <a href="https://api.drupal.org/api/drupal/core%21modules%21taxonomy%21src%21Plugin%21migrate%21D7TaxonomyTermDeriver.php/class/D7TaxonomyTermDeriver/9.0.x">D7TaxonomyTermDeriver</a>, <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21EntityReferenceTranslationDeriver.php/class/EntityReferenceTranslationDeriver/9.0.x">EntityReferenceTranslationDeriver</a>, <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%21migrate%21D6NodeDeriver.php/class/D6NodeDeriver/9.0.x">D6NodeDeriver</a>, and <a href="https://api.drupal.org/api/drupal/core%21modules%21taxonomy%21src%21Plugin%21migrate%21D6TermNodeDeriver.php/class/D6TermNodeDeriver/9.0.x">D6TermNodeDeriver</a>.</p> <h3 id="field_plugin_method">field_plugin_method key</h3> <p>A string value. This key must be set only in migrations that use Drupal\migrate_drupal\Plugin\migrate\FieldMigration as the plugin class. They take care of importing fields from previous versions of Drupal. The following is a list of possible values:</p> <ul> <li>alterFieldMigration as set by d7_field.yml.</li> <li>alterFieldFormatterMigration as set by d7_field_formatter_settings.yml.</li> <li>alterFieldInstanceMigration as set by d7_field_instance.yml.</li> <li>alterFieldWidgetMigration as set by d7_field_instance_widget_settings.yml</li> </ul> <p>There are Drupal 6 counterparts for these migrations. <span><span><span><span><span><span>Note that the field_plugin_method key is a replacement for the deprecated cck_plugin_method key.</span></span></span></span></span></span></p> <h3 id="provider">provider key</h3> <p>An array value. If set, it should contain a list of module machine names that must be enabled for this migration to work. Refer to the <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/content_translation/migrations/d7_entity_reference_translation.yml">d7_entity_reference_translation.yml</a> and <a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/content_translation/migrations/d6_entity_reference_translation.yml">d6_entity_reference_translation.yml</a> migrations for examples of possible values. This key rarely needs to be set. Usually the same module providing the migration definition file is the only one needed for the migration to work.</p> <h2 id="deriver-specific">Deriver specific configuration keys</h2> <p>It is possible that some derivers require extra configuration keys to be set. For example, the <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate_drupal%21src%21Plugin%21migrate%21EntityReferenceTranslationDeriver.php/class/EntityReferenceTranslationDeriver/9.0.x">EntityReferenceTranslationDeriver</a> the target_types to be set. Refer to the d7_entity_reference_translation.yml and d6_entity_reference_translation.yml migrations for examples of possible values. These migrations are also interesting because the source, process, and destination keys are not configured in the YAML definition files. They are actually set dynamically by the deriver.</p> <h2 id="configuration-entity">Migration configuration entity keys</h2> <p>The following keys should be used only if the migration is created as a configuration entity using the <a href="https://www.drupal.org/project/migrate_plus">Migrate Plus module</a>. Only the migration_group key is specific to migrations as configuration entities. All other keys apply for any configuration entity in Drupal. Refer to the <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Config%21Entity%21ConfigEntityBase.php/class/ConfigEntityBase/9.0.x">ConfigEntityBase</a> abstract class for more details on how they are used.</p> <h3 id="migration_group">migration_group key</h3> <p>A string value. If set, it should correspond to the id key of a migration group configuration entity. This allows inheriting configuration values from the group. For example, the database connection for the source configuration. Refer to this article for more information on <a href="https://agaric.coop/blog/using-migration-groups-share-configuration-among-drupal-migrations">sharing configuration using migration groups</a>. They can be used to import or rollback all migrations within a group using the following Drush commands provided by the Migrate Tools module:</p> <pre> <code class="language-bash">$ drush migrate:import --group='udm_config_group_json_source' $ drush migrate:rollback --group='udm_config_group_json_source'</code></pre><h3 id="uuid">uuid key</h3> <p>A string value. The value should be a UUID v4. If not set, the configuration management system will create a UUID on the fly and assign it to the migration entity. Refer to this article for more details on setting <a href="https://agaric.coop/blog/defining-drupal-migrations-configuration-entities-migrate-plus-module#uuid">UUIDs for migrations defined as configuration entities</a>.</p> <h3 id="langcode">langcode key</h3> <p>A string value. The language code of the entity's default language. English is assumed by default. For example: en.</p> <h3 id="status">status key</h3> <p>A boolean value. The enabled/disabled status of the configuration entity. For example: true.</p> <h3 id="dependencies">dependencies key</h3> <p>A nested array value. Configuration entities can declare dependencies on modules, themes, content entities, and other configuration entities. These dependencies can be recalculated on save operations or enforced. Refer to the <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Config%21Entity%21ConfigDependencyManager.php/class/ConfigDependencyManager/9.0.x">ConfigDependencyManager</a> class’ documentation for details on how to configure this key. One practical use of this key is to automatically remove the migration (configuration entity) when the module that defined it is uninstalled. To accomplish this, you need to set an enforced module dependency on the same module that provides the migration. This is explained in the article on <a href="https://agaric.coop/blog/defining-drupal-migrations-configuration-entities-migrate-plus-module#delete">defining Drupal migrations as configuration entities</a>. For reference, below is a code snippet from that article showing how to configure this key:</p> <pre> <code class="language-yaml">uuid: b744190e-3a48-45c7-97a4-093099ba0547 id: udm_config_json_source_node_local label: 'UD migrations configuration example' dependencies: enforced: module: - ud_migrations_config_json_source</code></pre><h2 id="migration-group">Migration group configuration entity keys</h2> <p>Migration groups are also configuration entities. That means that they can have uuid, langcode, status, and dependencies keys as explained before. Additionally, the following keys can be set. These other keys can be set for migration groups:</p> <h3 id="group-id">id key</h3> <p>A required string value. It serves as the identifier for the migration group. The value should be a machine name.</p> <h3 id="group-label">label key</h3> <p>A string value. The human-readable label for the migration group.</p> <h3 id="group-description">description key</h3> <p>A string value. More information about the group.</p> <h3 id="group-source-type">source_type key</h3> <p>A string value. Short description of the type of source. For example: "Drupal 7" or "JSON source".</p> <h3 id="group-module">module key</h3> <p>A string value. The machine name of a dependent module. This key rarely needs to be set. A configuration entity is always dependent on its provider, the module defining the migration group.</p> <h3 id="group-shared-configuration">shared_configuration key</h3> <p>A nested array value. Any configuration key for a migration can be set under this key. Those values will be inherited by any migration associated with the current group. Refer to this article for more information on <a href="https://agaric.coop/blog/using-migration-groups-share-configuration-among-drupal-migrations">sharing configuration using migration groups</a>. The following is an example from the <a href="https://github.com/dinarcon/ud_migrations/tree/master/ud_migrations_config_group_json_source">ud_migrations_config_group_json_source module</a> from the article on <a href="https://agaric.coop/blog/executing-drupal-migrations-user-interface-migrate-tools">executing migrations from the Drupal interface</a>.</p> <pre> <code class="language-yaml">uuid: 78925705-a799-4749-99c9-a1725fb54def id: udm_config_group_json_source label: 'UD Config Group (JSON source)' description: 'A container for migrations about individuals and their favorite books. Learn more at https://understanddrupal.com/migrations.' source_type: 'JSON resource' shared_configuration: dependencies: enforced: module: - ud_migrations_config_group_json_source migration_tags: - UD Config Group (JSON Source) - UD Example source: plugin: url data_fetcher_plugin: file data_parser_plugin: json urls: - modules/custom/ud_migrations/ud_migrations_config_group_json_source/sources/udm_data.json</code></pre><p>What did you learn in today’s article? Did you know there were so many configuration options for migration definition files? Were you aware that some keys apply only when migrations are defined as configuration entities? Have you used migrations groups to share configuration across migrations? Share your answers in the comments. Also, I would be grateful if you shared this blog post with friends and colleagues.</p> </div> 2020 July 02 Mauricio Dinarte https://agaric.coop/Drupal%20migrations%20reference%3A%20List%20of%20configuration%20options%20in%20YAML%20definition%20files Free Drupal 9 webinars on site building, migrations, and upgrades https://agaric.coop/ <div class="flow_middle"> <p>On Tuesday, July 7, Agaric will host 3 free online webinars about Drupal 9. We invite the community to join us to learn more about the latest version of our favorite CMS. We will leave time at the end of each presentation for questions from the audience. All webinars will be presented online via Zoom. Fill out the form at the end of the post to reserve your seat. We look forward to seeing you.</p> <h2>Getting started Drupal 9</h2> <p>Time: 10:00 AM - 11:00 AM Eastern Time (EDT)</p> <p>This webinar will cover basic site building concepts. You will learn what is a node and how they differ from content types. We are going to explain why fields are so useful for structuring your site's content and the benefits of doing this. We will cover how to use Views to create listing of content. Layout builder, blocks, taxonomies, and the user permissions system will also be explained.</p> <h2>Introduction to Drupal 9 migrations</h2> <p>Time: 11:30 AM - 12:30 AM Eastern Time (EDT)</p> <p>This webinar will present an overview of the Drupal migrations system. You will learn about how the Migrate API works and what assumptions it makes. We will explain the syntax to write migrations how different source, process, and destinations plugins work. Recommended migration workflows and debugging tips will also be presented. No previous experience with the Migrate API nor PHP is required to attend.</p> <h2>Drupal 9 upgrades: how and when to move your Drupal 7 sites?</h2> <p>Time: 1:00 PM - 2:00 PM Eastern Time (EDT)</p> <p>This webinar will present different tools and workflows to upgrade your Drupal 7 site to Drupal 9. We will run through what things to consider when planning an upgrade. This will include how to make site architecture changes, modules that do not have D9 counterparts, what to do when there are no automated upgrade paths.</p> <p> </p> <p>Below are the slides used during the webinars:</p> </div> , <div class="flow_middle"> <span class="file file--mime-application-pdf file--application-pdf"> <span class="icon"> <i class="fa fa-file-pdf-o"></i> </span> <a href="/sites/default/files/2020-07/Getting%20started%20Drupal%209%20-%20Agaric%20webinar%20July%202020.pdf" type="application/pdf">Getting started Drupal 9 - Agaric webinar July 2020.pdf</a> </span> <span class="file file--mime-application-pdf file--application-pdf"> <span class="icon"> <i class="fa fa-file-pdf-o"></i> </span> <a href="/sites/default/files/2020-07/Introduction%20to%20Drupal%209%20migrations%20-%20Agaric%20webinar%20July%202020.pdf" type="application/pdf">Introduction to Drupal 9 migrations - Agaric webinar July 2020.pdf</a> </span> <span class="file file--mime-application-pdf file--application-pdf"> <span class="icon"> <i class="fa fa-file-pdf-o"></i> </span> <a href="/sites/default/files/2020-07/Drupal%209%20upgrades%20-%20Agaric%20webinar%20July%202020.pdf" type="application/pdf">Drupal 9 upgrades - Agaric webinar July 2020.pdf</a> </span> </div> , <div class="flow_middle"> <p>Agaric is also offering <a href="https://agaric.coop/training">full-day trainings</a> for these topics later this month. Dates, prices, more details, and registration options:</p> </div> , <div class="flow_middle"> <div data-history-node-id="295" class="node node--type-landing-page node--view-mode-simple-card is-mobile columns clearfix"> <div class="column"> <h2 class="title is-size-3-desktop is-size-4-tablet is-size-5-mobile title is-5"> <a href="/training/getting-started-drupal-9" hreflang="en">Getting started with Drupal 9</a> </h2> </div> <div class="column"> <a href="/training/getting-started-drupal-9"> <img srcset="/sites/default/files/styles/max_325x325/public/2020-06/looking-at-map.jpeg?itok=tsrf9k37 325w, /sites/default/files/styles/max_650x650/public/2020-06/looking-at-map.jpeg?itok=6L9aAZRd 650w, /sites/default/files/styles/max_1300x1300/public/2020-06/looking-at-map.jpeg?itok=y3caBtMa 1300w" sizes="(min-width: 1290px) 325px, (min-width: 851px) 25vw, (min-width: 560px) 50vw, 100vw" src="/sites/default/files/styles/max_325x325/public/2020-06/looking-at-map.jpeg?itok=tsrf9k37" alt="Woman looking at a map." typeof="foaf:Image" /> </a> </div> </div> <div data-history-node-id="293" class="node node--type-landing-page node--view-mode-simple-card is-mobile columns clearfix"> <div class="column"> <h2 class="title is-size-3-desktop is-size-4-tablet is-size-5-mobile title is-5"> <a href="/training/drupal-content-migrations" hreflang="en">Drupal 9 content migrations</a> </h2> </div> <div class="column"> <a href="/training/drupal-content-migrations"> <img srcset="/sites/default/files/styles/max_325x325/public/2020-06/bird-migration.jpeg?itok=NUJLjUr6 325w, /sites/default/files/styles/max_650x650/public/2020-06/bird-migration.jpeg?itok=ZL_G03hm 650w, /sites/default/files/styles/max_1300x1300/public/2020-06/bird-migration.jpeg?itok=NuDMKziu 1300w" sizes="(min-width: 1290px) 325px, (min-width: 851px) 25vw, (min-width: 560px) 50vw, 100vw" src="/sites/default/files/styles/max_325x325/public/2020-06/bird-migration.jpeg?itok=NUJLjUr6" alt="Birds flying" typeof="foaf:Image" /> </a> </div> </div> <div data-history-node-id="294" class="node node--type-landing-page node--view-mode-simple-card is-mobile columns clearfix"> <div class="column"> <h2 class="title is-size-3-desktop is-size-4-tablet is-size-5-mobile title is-5"> <a href="/training/upgrading-drupal-using-migrate-api" hreflang="en">Upgrading to Drupal 9 using the Migrate API</a> </h2> </div> <div class="column"> <a href="/training/upgrading-drupal-using-migrate-api"> <img srcset="/sites/default/files/styles/max_325x325/public/2020-06/rocket-launch.jpeg?itok=mW_5YypR 325w, /sites/default/files/styles/max_650x650/public/2020-06/rocket-launch.jpeg?itok=aavIYACj 650w, /sites/default/files/styles/max_1300x1300/public/2020-06/rocket-launch.jpeg?itok=00TPvRqT 1300w" sizes="(min-width: 1290px) 325px, (min-width: 851px) 25vw, (min-width: 560px) 50vw, 100vw" src="/sites/default/files/styles/max_325x325/public/2020-06/rocket-launch.jpeg?itok=mW_5YypR" alt="Rocket launch." typeof="foaf:Image" /> </a> </div> </div> </div> 2020 July 01 Michele Metts https://agaric.coop/Free%20Drupal%209%20webinars%20on%20site%20building%2C%20migrations%2C%20and%20upgrades Show and Tell con Agaric - Compartir el Trabajo con Otras Cooperativas https://agaric.coop/ <div class="flow_middle"> <p><span><img alt="Sharing work between cooperatives." src="/sites/default/files/inline-images/undraw_pizza_sharing_wxop.png" /><span> </span></span></p> <p>Agaric organiza una reunión semanal en línea conocida como "Show and Tell". Los participantes comparten consejos y trucos que hemos aprendido y plantean preguntas a otros desarrolladores sobre tareas o proyectos en los que estamos trabajando. Cada semana pedimos a la gente que nos envíe un poco de información sobre lo que les gustaría presentar. Esto no es un requisito previo, sólo una sugerencia. Tener un aviso previo de las presentaciones nos permite correr la voz a otros que puedan estar interesados, pero usted puede simplemente presentarse, y lo más probable es que haya tiempo para presentar durante 5-10 minutos. <a href="https://lists.mayfirst.org/mailman/listinfo/showandtell">Puede inscribirse en la lista de correo de "Show and Tell"</a> y será notificado de los próximos eventos.</p> <p>Recientemente hemos abierto el chat de "Show and Tell" para vincularnos con otras cooperativas que hacen trabajos de desarrollo web. Agaric fue contactada por miembros de Fiqus.coop en Argentina, ya que ellos ya habían comenzado con una iniciativa para conocer a otros desarrolladores de cooperativas y asi, compartir valores y metas. Nadie había enviado un aviso de presentación, así que cambiamos el tema del chat para que fuera más un encuentro y un saludo para conocernos mejor con el objetivo de poder compartir nuestro trabajo en los proyectos. El valor de la reunión se hizo evidente de inmediato  cuando nos adentramos en la conversación con algunos miembros de Fiqus.</p> <p>A continuación, invitamos a más desarrolladores a participar en la discusión y se abrieron las puertas para compartir más profundamente y conectar. Esta semana nuestra reunión fue exagerada! Nicolás Dimarco nos guió a través de <a href="https://github.com/fiqus/FIT-talk-en">una corta presentación de diapositivas</a> que reveló un proceso y flujo de trabajo federado para compartir el desarrollo con los miembros de múltiples cooperativas. El plan es tan simple que todos lo entendieron inmediatamente. La conversación que siguió fue convincente, las preguntas fueron indicativas de dónde necesitamos educar a los demás sobre los principios cooperativos vs. las tácticas corporativas. Necesitamos más discusión sobre la confianza y la amistad. Hay tantos desarrolladores en trabajos corporativos que me han preguntado cómo funciona una cooperativa de desarrollo web y cómo funciona un proyecto sin un gerente. Primero, me gusta explicar que los proyectos tienen gerentes, pero ellos están manejando el trabajo, no a la gente. Tomarse el tiempo para conocer las habilidades y pasiones de cada uno sobre la programación es una parte esencial para poder trabajar juntos en una Federación. Fiqus.coop ha hecho que sea sencillo para todos ver el camino para compartir el trabajo en los proyectos!</p> <p>Aquí está el enlace a la <a href="http://bit.ly/2U6tjVH">grabación de video</a> del chat donde Nicolás Dimarco de Fiqus.coop presenta la fórmula de trabajo federado entre cooperativas. Aquí hay un enlace a las notas de la reunión del 20/3/2019 y algunas reuniones pasadas de Show and Tell.</p> <p><a href="https://agaric.coop/show-and-tell">Más información sobre el Show and Tell.</a></p> <p>Algunas tiendas de Drupal ya trabajan juntas en proyectos y podemos ayudar a que crezcan compartiendo nuestras experiencias.  Nos encantaría saber cómo trabajan y los procesos que han descubierto que hacen que compartir el trabajo en los proyectos sea un éxito!</p> </div> , <div class="flow_middle"> <p> <label class="label visually-hidden">Video embed</label> <strong><iframe width="854" height="480" frameborder="0" allowfullscreen="allowfullscreen" src="https://www.youtube.com/embed/dRErW6jEjSM?autoplay=0&amp;start=0&amp;rel=0"></iframe> </strong> </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="2019-04-24T12:00:00Z">April 24, 2019</time> </div> <div class="column"> <p><strong>Notas:</strong></p> <p>Diapositivas - <a href="https://github.com/fiqus/FIT-talk-en">https://github.com/fiqus/FIT-talk-en</a></p> <p> </p> <p>## FACTTIC - Federación Argentina de Cooperativas de Trabajo de Tecnología, Innovación y Conocimiento</p> <p>* Lista de correo sólo para miembros</p> <p>* Mattermost (chat de código abierto)</p> <p>* Reunión mensual de la junta virtual (cualquier miembro puede asistir)</p> <p>* Reuniones anuales cara a cara</p> <p> </p> <p>## FIT</p> <p>* Un proyecto dentro de FACTTIC donde las cooperativas comparten el estado de los proyectos en los que están trabajando</p> <p>* Evolucionado a un área donde las cooperativas comparten proyectos</p> <p>* Para unirse a la FIT, tienes que ser un miembro FACTTIC</p> <p>* Reuniones virtuales mensuales</p> <p>* Canal Mattermost para el diálogo en curso</p> <p>* Las cooperativas tienen diferentes habilidades/servicios, pero cuando hay superposición, trata de no competir entre sí y determina las "necesidades" de cada cooperativa.</p> <p> </p> <p>### Escenario 1: El proyecto demanda más trabajadores de los que tiene la cooperativa</p> <p>* Cuando hay una necesidad de ayuda, el proyecto se comparte en FIT</p> <p>* Las cooperativas pueden solicitar unirse al proyecto</p> <p>* Los candidatos son evaluados y uno es elegido</p> <p>* El cliente es informado y debe estar de acuerdo</p> <p>* La coordinación del proyecto está dirigida por la cooperativa inicial</p> <p>* El acuerdo comercial es manejado sólo por la cooperativa inicial</p> <p> </p> <p>### Escenario 2: El cliente necesita que se haga el trabajo, pero Coop decide no tomarlo</p> <p>* Esto podría suceder porque la cooperativa inicial no tiene los recursos o declina por una razón estratégica</p> <p>* El proyecto se comparte con la FIT</p> <p>* Si no hay cooperativas interesadas, se le dice al cliente que no hay nadie disponible.</p> <p>* Si una cooperativa está interesada, el contacto de esa cooperativa se comparte con el cliente.  </p> <p>* Si más de una cooperativa está interesada, entonces preguntamos, ¿este proyecto requiere más de un trabajador?</p> <p>  * Si sólo necesita un trabajador, entonces la cooperativa que más lo necesita lo recibe.</p> <p>  * Si requiere más de un trabajador, entonces las cooperativas se coordinan entre sí para completar el trabajo.</p> <p> </p> <p>## Case Studies</p> <p> </p> <p>### Betterez</p> <p>* Cliente canadiense</p> <p>* Plataforma de gestión de reservas y billetes</p> <p>* Tecnologías: MongoDB, NodeJS, VueJS y Elixir</p> <p>* Necesitaba más trabajo del que Fiqus podía proporcionar</p> <p>* 30 desarrolladores con 7 cooperativas diferentes</p> <p>* Fiqus maneja las cuestiones financieras como las diferentes tarifas de los diferentes servicios</p> <p> </p> <p>### Receptivi</p> <p>* Cliente canadiense</p> <p>* La página web muestra en tiempo real las percepciones psicológicas del personal</p> <p>* Tenía más trabajo, pero Fiqus se negó a asumir el trabajo</p> <p>* El trabajo se compartió en la FIT</p> <p>* 3 desarrolladores de 2 cooperativas</p> <p> </p> <p>### Mall Plaza</p> <p>* Cliente chileno</p> <p>* Aplicación móvil que muestra los servicios del centro comercial</p> <p>*Técnica: Reaccionar Nativo, PostgreSQL, Frasco</p> <p> </p> <p>## Onapsis</p> <p>* Cliente argentino</p> <p>* Sistema web que muestra alertas de vulnerabilidad en los servidores</p> <p>* 2 cooperativas</p> <p> </p> <p>## FIT Internacional</p> <p>* Queremos replicar este modelo a nivel internacional.</p> <p>1. Compartir este modelo con otros a fin de mejorarlo y difundir la conciencia</p> <p>  * Presentando en el Show and Tell</p> <p>2. Construir relaciones de confianza</p> <p>3. Conocerse en persona, pasar tiempo juntos</p> <p>  * Viajando al Reino Unido, reunión con la federación, COTECH</p> <p>  * Compartir experiencias después del viaje</p> <p> </p> <p>## Preguntas y respuestas:</p> <p>P: ¿Alguna vez ha tenido la resistencia de un cliente al entregar el trabajo a otra cooperativa?</p> <p>  R: Hay veces que los clientes no entienden las cooperativas y la cooperación entre ellas. Explicamos el beneficio y compartimos estudios de casos. Si hay una fecha límite que debe cumplirse, es más rápido traer un equipo con experiencia previa trabajando con la empresa original que tratar de encontrar una empresa completamente diferente.</p> <p>P: ¿Cuánto comparten acerca de las múltiples cooperativas que trabajan en un proyecto?</p> <p>  R: Si son sólo unas pocas horas, no vale la pena hablar de ello. Sin embargo, la mayoría de las veces es importante compartir esa información y usarla como una oportunidad educativa para demostrar la fuerza de las cooperativas trabajando juntas.</p> <p>* Una vez que los clientes ven el resultado de la cooperación se dan cuenta de que es una buena manera de enfocar el trabajo.    </p> <p>* La simplicidad del proceso es hermosa.</p> <p>P: ¿Cómo se comparten los costos de desarrollo de los negocios?</p> <p>  R: La cooperativa que comparte el proyecto puede bajar sus tarifas durante el proceso de aceleración.</p> <p>  * Este es un aspecto que podría mejorarse.</p> <p>  * Lo más importante es ser transparente y comunicar mucho.</p> <p>  * Mantener el espíritu de generosidad fluyendo.</p> <p>* Cuando la cooperación es exitosa, se construye la confianza con el cliente.</p> <p>* Usar una herramienta para analizar los presupuestos y el progreso de los proyectos y prever la disponibilidad.</p> <p>* La cooperación también asegura la calidad, los trabajadores de confianza se unirán al proyecto.</p> <p> </p> <p> </p> </div> </div> 2020 June 26 Michele Metts https://agaric.coop/Show%20and%20Tell%20con%20Agaric%20-%20Compartir%20el%20Trabajo%20con%20Otras%20Cooperativas Online trainings on Drupal migration and upgrades https://agaric.coop/ <div class="flow_middle"> <p>Agaric is excited to announce online training on Drupal migrations and upgrades. In July 2020, we will offer three trainings: <a href="https://agaric.coop/training/drupal-content-migrations">Drupal 8/9 content migrations</a>, <a href="https://agaric.coop/training/upgrading-drupal-using-migrate-api">Upgrading to Drupal 8/9 using the Migrate API</a>, and <a href="https://agaric.coop/training/getting-started-drupal-9">Getting started with Drupal 9</a>.</p> <p>We have been providing training for years at Drupal events and privately for clients. At DrupalCon Seattle 2019, our migration training was sold out with 40+ attendees and received very positive feedback. We were scheduled to present two trainings at DrupalCon Minneapolis 2020: one on Drupal migrations and the other on Drupal upgrades. When the conference pivoted to an online event, all trainings were cancelled. To fill the void, we are moving the full training experience online for individuals and organizations who want to learn how to plan and execute successful Drupal migration/upgrade projects.</p> <h2>Up to date with Drupal 9</h2> <p>Drupal is always evolving and the Migrate API is no exception. New features and improvements are added all the time. We regularly update our curriculum to cover the latest changes in the API. This time, both trainings will use Drupal 9 for all the examples! If you are still using Drupal 8, don't worry as the example code is compatible with both major versions of Drupal. We will also cover the differences between Drupal 8 and 9.</p> <h2>Drupal 8/9 content migrations</h2> <p>In this training you will learn to move content into Drupal 8 and 9 using the Migrate API. An overview of the Extract-Transform-Load (ETL) pattern that migrate implements will be presented. Source, process, and destination plugins will be explained to show how each affects the migration process. By the end of the workshop, you will have a better understanding on how the migrate ecosystem works and the thought process required to plan and perform migrations. All examples will use YAML files to configure migrations. No PHP coding required.</p> <p>Date: Tuesday, July 21, 2020<br /> Time: 9 AM – 5 PM Eastern time<br /> Cost: $500 USD</p> <p><a href="https://agaric.coop/training/drupal-content-migrations"><strong>Click here to register</strong></a></p> <h2>Upgrading to Drupal 8/9 using the Migrate API</h2> <p>In this training you will learn to use the Migrate API to upgrade your Drupal 6/7 site to Drupal 8/9. You will practice different migration strategies, accommodate changes in site architecture, get tips on troubleshooting issues, and much more. After the training, you will know how to plan and execute successful upgrade projects.</p> <p>Date: Thursday, July 23, 2020<br /> Time: 9 AM – 5 PM Eastern time<br /> Cost: $500 USD</p> <p><a href="https://agaric.coop/training/upgrading-drupal-using-migrate-api"><strong>Click here to register</strong></a></p> <h2>Getting started with Drupal 9</h2> <p>We are also offering a training for people who want to get a solid foundation in Drupal site building. Basic concepts will be explained and put into practice through various exercises. The objective is that someone, who might not even know about Drupal, can understand the different concepts and building blocks to create a website. A simple, fully functional website will be built over the course of the day-long class.</p> <p>Date: Monday, July 13, 2020<br /> Time: 9 AM – 5 PM Eastern time<br /> Cost: $250 USD</p> <p><a href="https://agaric.coop/training/getting-started-drupal-9"><strong>Click here to register</strong></a></p> <h2>Discounts and scholarships available</h2> <p>Anyone is eligible for a 15% discount on their second training. Additionally, if you are a member of an under-represented community who cannot afford the full price of the training, we have larger discounts and full scholarship available. <a href="https://agaric.coop/ask">Ask Agaric</a> to learn more about them.</p> <h2>Customized training available</h2> <p>We also offer customized training for you or your team's specific needs. Site building, module development, theming, and data migration are some of the topics we cover. Check out our <a href="https://agaric.coop/training">training page</a> or <a href="https://agaric.coop/ask">ask Agaric</a> for more details. Custom training can be delivered online or on-site in English or Spanish.</p> <h2>Meet your lead trainer</h2> <p><a href="https://www.drupal.org/u/dinarcon">Mauricio Dinarte</a> is a frequent speaker and trainer at conferences around the world. He is passionate about Drupal, teaching, and traveling. Over the last few years, he has presented 30+ sessions and full-day trainings at 20+ DrupalCamps and DrupalCons over America and Europe. In August 2019, he wrote an article every day to share his expertise on Drupal migrations.</p> <p>We look forward to seeing you online in July at <a href="https://agaric.coop/training">any or all of these trainings</a>!</p> </div> 2020 June 23 Michele Metts https://agaric.coop/Online%20trainings%20on%20Drupal%20migration%20and%20upgrades Keep modules compatible with multiple Drupal Core branches while using new services https://agaric.coop/ <div class="flow_middle"> <p>While working on making a module compatible with Drupal 9 I found that the module was using an obsolete function that had been replaced with a new service. It was something like this:</p> <pre> <code class="lang-php"> /** * My plugin. * * @SearchPlugin( * id = "my_plugin", * title = @Translation("My plugin") * ) */ class MyPluginSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface { /** * {@inheritdoc} */ public static function create( ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition ) { return new static( $configuration, $plugin_id, $plugin_definition ); } /** ... **/ public function indexClear() { search_index_clear($this-&gt;getPluginId()); } } </code> </pre><p>The function <code>search_index_clear</code> is now part of the new <code>search.index</code> service that was added in Drupal 8.8. In order to keep this working on Drupal 8.8+ and Drupal 9 we need to inject the service in the <code>create</code> function. But if we do this unconditionally, we will get an error in Drupal 8.7 because that service was added on 8.8. What to do then?</p> <p>Fortunately years ago I read an article that addressed a similar need. It talked about how to <a href="https://www.previousnext.com.au/blog/safely-extending-drupal-8-plugin-classes-without-fear-of-constructor-changes">safely extends Drupal 8 plugin classes without fear of constructor changes</a>. In my case I didn't want to change the constructor, so as to keep it compatible with Drupal 8.7 and below. At the same time, I wanted to inject the new service to use it in Drupal 8.8+ and Drupal 9. I just modified a bit my code to something like this:</p> <p> </p> <pre> <code class="lang-php"> /** * My plugin. * * @SearchPlugin( * id = "my_plugin", * title = @Translation("My plugin") * ) */ class MyPluginSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface { /** ... */ protected $searchIndex; /** * {@inheritdoc} */ public static function create( ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition ) { $instance = new static( $configuration, $plugin_id, $plugin_definition ); // Only inject the service in Drupal 8.8 or newer. if (floatval(\Drupal::VERSION) &gt;= 8.8) { $instance-&gt;searchIndex = $container-&gt;get('search.index'); } return $instance; } /** ... **/ public function indexClear() { if (floatval(\Drupal::VERSION) &gt;= 8.8) { $this-&gt;searchIndex-&gt;clear($this-&gt;getPluginId()); } else { search_index_clear($this-&gt;getPluginId()); } } } </code> </pre><p>And that's it, Drupal 8.8 and newer will take advantage of the new service while we keep this compatible with Drupal 8.7. This will give users more time to upgrade to Drupal 8.8+ or Drupal 9.</p> </div> 2020 June 08 David Valdez https://agaric.coop/Keep%20modules%20compatible%20with%20multiple%20Drupal%20Core%20branches%20while%20using%20new%20services Drupal migrations reference: List of properties per Commerce content entity https://agaric.coop/ <div class="flow_middle"> <p>In a previous article, we presented a<span> </span><a href="/blog/drupal-migrations-reference-list-properties-content-entity" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">list of properties per content entity</a><span> </span>in Drupal core and some contributed modules. This time we will provide a similar list for<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal Commerce</a>. When migrating into content entities, these define several properties that can be included in the <code>process</code> section to populate their values. For example, when importing Drupal Commerce product variations you can specify the SKU, price, list price, etc. In the case of promotions, you can set the start and end dates. Finding out which properties are available for an entity might require some Drupal development knowledge. To make the process easier, in today’s article we are presenting a reference of properties available in content entities provided by Drupal Commerce and some related contributed modules.</p> <div class="block block-system block-system-main-block" id="block-bartik-content" style="color: rgb(59, 59, 59); font-family: Georgia, &quot;Times New Roman&quot;, Times, serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> <div class="content" style="margin-top: 10px;"> <article class="node node--type-article node--promoted node--view-mode-full clearfix" data-history-node-id="41" role="article" style="display: block;"> <p> </p> <p> </p> <p> </p> <p> </p> <div class="node__content clearfix" style="margin-top: 10px; font-size: 1.071em;"> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"> <p style="margin: 0px 0px 1.2em;"><img alt="List of Drupal Commerce content entities." data-entity-type="file" data-entity-uuid="ce5ef3d0-b5b8-491a-bf58-08407322a90f" src="/sites/default/files/inline-images/migrations-properties-per-commerce-content-entity-list.jpg" width="960" height="540" loading="lazy" /></p> <p style="margin: 0px 0px 1.2em;">For each entity we will present: the module that provides it, the class that defines it, and the available properties. For each property we will list its name, field type, a description, and a note if the field allows unlimited values (i.e. it has an unlimited cardinality). The list of properties available for a content entity depend on many factors. For example, if the entity is revisionable (e.g. <code>revision_default</code>), translatable (e.g. <code>langcode</code>), or both (e.g. <code>revision_translation_affected</code>). The modules that are enabled on the site can also affect the available properties. For instance, if the “Workspaces” module is installed, it will add a <code>workspace</code> property to many content entities. This reference assumes that Drupal was installed using the <code>standard</code> installation profile and only Drupal Commerce related modules that provide content entities are enabled.</p> <p style="margin: 0px 0px 1.2em;">It is worth noting that entity properties are divided in two categories: base field definitions and field storage configurations. Base field configurations will always be available for the entity. On the other hand, the presence of field storage configurations will depend on various factors. For one, they can only be added to fieldable entities. Attaching the fields to the entity can be done manually by the user, by a module, or by an installation profile. Again, this reference assumes that Drupal was installed using the <code>standard</code> installation profile with Drupal Commerce related modules enabled. By default, the <code>commerce_product</code> entity adds a <code>body</code>field. For entities that can have multiple bundles, not all properties provided by the field storage configurations will be available in all bundles. For example, with the <code>standard</code> installation profile all content types will have a <code>body</code> field associated with it, but only the <code>article</code> content type has the <code>field_image</code>, and <code>field_tags</code> fields. If<span> </span><a href="/blog/drupal-migrations-reference-list-subfields-field-type" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">subfields are available for the field type</a>, you can migrate into them.</p> <p style="margin: 0px 0px 1.2em;">If you are migrating into Drupal Commerce, make sure to check the<span> </span><a href="https://www.drupal.org/project/commerce_migrate" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Commerce Migrate module</a>. It offers migrate destination field handlers for commerce fields and a plugin for commerce product types. It also provides a migration path from Commerce 1 (Drupal 7), Ubercart, and other e-commerce platforms. It is even possible to import data from other platforms like WooCommerce, Magento, and Shopify via CSV exports.</p> <h2 id="commerce_store" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Store entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Store (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/store/src/Entity/Store.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_store\Entity\Store</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">store_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">langcode</strong>: (language) Language.</li> <li><strong style="font-weight: bold;">type</strong>: (entity_reference to commerce_store_type) Type. The store type.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Owner. The store owner.</li> <li><strong style="font-weight: bold;">name</strong>: (string) Name. The store name.</li> <li><strong style="font-weight: bold;">mail</strong>: (email) Email. Store email notifications are sent from this address.</li> <li><strong style="font-weight: bold;">default_currency</strong>: (entity_reference to commerce_currency) Default currency. The default currency of the store.</li> <li><strong style="font-weight: bold;">timezone</strong>: (list_string) Timezone. Used when determining promotion and tax availability.</li> <li><strong style="font-weight: bold;">address</strong>: (address) Address. The store address.</li> <li><strong style="font-weight: bold;">billing_countries</strong>: (list_string) Supported billing countries. Allows unlimited values.</li> <li><strong style="font-weight: bold;">path</strong>: (path) URL alias. The store URL alias.</li> <li><strong style="font-weight: bold;">is_default</strong>: (boolean) Default. Whether this is the default store.</li> <li><strong style="font-weight: bold;">default_langcode</strong>: (boolean) Default translation. A flag indicating whether this is the default translation.</li> <li><strong style="font-weight: bold;">shipping_countries</strong>: (list_string) Supported shipping countries. Allows unlimited values.</li> <li><strong style="font-weight: bold;">prices_include_tax</strong>: (boolean) Prices are entered with taxes included.</li> <li><strong style="font-weight: bold;">tax_registrations</strong>: (list_string) Tax registrations. The countries where the store is additionally registered to collect taxes. Allows unlimited values.</li> </ol> <h2 id="commerce_product" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Product entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Product (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/product/src/Entity/Product.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_product\Entity\Product</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">product_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">langcode</strong>: (language) Language.</li> <li><strong style="font-weight: bold;">type</strong>: (entity_reference to commerce_product_type) Product type.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Published.</li> <li><strong style="font-weight: bold;">stores</strong>: (entity_reference to commerce_store) Stores. The product stores. Allows unlimited values.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Author. The product author.</li> <li><strong style="font-weight: bold;">title</strong>: (string) Title. The product title.</li> <li><strong style="font-weight: bold;">variations</strong>: (entity_reference to commerce_product_variation) Variations. The product variations. Allows unlimited values.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the product was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the product was last edited.</li> <li><strong style="font-weight: bold;">default_langcode</strong>: (boolean) Default translation. A flag indicating whether this is the default translation.</li> </ol> <p style="margin: 0px 0px 1.2em;">List of field storage configurations:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">body</strong>: text_with_summary field.</li> </ol> <h2 id="commerce_product_variation" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Product variation entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Product (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/product/src/Entity/ProductVariation.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_product\Entity\ProductVariation</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">variation_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">langcode</strong>: (language) Language.</li> <li><strong style="font-weight: bold;">type</strong>: (entity_reference to commerce_product_variation_type) Product variation type.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Published.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Author. The variation author.</li> <li><strong style="font-weight: bold;">product_id</strong>: (entity_reference to commerce_product) Product. The parent product.</li> <li><strong style="font-weight: bold;">sku</strong>: (string) SKU. The unique, machine-readable identifier for a variation.</li> <li><strong style="font-weight: bold;">title</strong>: (string) Title. The variation title.</li> <li><strong style="font-weight: bold;">list_price</strong>: (commerce_price) List price. The list price.</li> <li><strong style="font-weight: bold;">price</strong>: (commerce_price) Price. The price</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the variation was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the variation was last edited.</li> <li><strong style="font-weight: bold;">default_langcode</strong>: (boolean) Default translation. A flag indicating whether this is the default translation.</li> </ol> <h2 id="commerce_product_attribute_value" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Product attribute value entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Product (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/product/src/Entity/ProductAttributeValue.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_product\Entity\ProductAttributeValue</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">attribute_value_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">langcode</strong>: (language) Language.</li> <li><strong style="font-weight: bold;">attribute</strong>: (entity_reference to commerce_product_attribute) Attribute.</li> <li><strong style="font-weight: bold;">name</strong>: (string) Name. The attribute value name.</li> <li><strong style="font-weight: bold;">weight</strong>: (integer) Weight. The weight of this attribute value in relation to others.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the attribute value was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the attribute value was last edited.</li> <li><strong style="font-weight: bold;">default_langcode</strong>: (boolean) Default translation. A flag indicating whether this is the default translation.</li> </ol> <h2 id="commerce_order" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Order entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Order (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/order/src/Entity/Order.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_order\Entity\Order</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">order_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (entity_reference to commerce_order_type) Order type.</li> <li><strong style="font-weight: bold;">order_number</strong>: (string) Order number. The order number displayed to the customer.</li> <li><strong style="font-weight: bold;">store_id</strong>: (entity_reference to commerce_store) Store. The store to which the order belongs.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Customer. The customer.</li> <li><strong style="font-weight: bold;">mail</strong>: (email) Contact email. The email address associated with the order.</li> <li><strong style="font-weight: bold;">ip_address</strong>: (string) IP address. The IP address of the order.</li> <li><strong style="font-weight: bold;">billing_profile</strong>: (entity_reference_revisions) Billing information. Billing profile</li> <li><strong style="font-weight: bold;">order_items</strong>: (entity_reference to commerce_order_item) Order items. The order items. Allows unlimited values.</li> <li><strong style="font-weight: bold;">adjustments</strong>: (commerce_adjustment) Adjustments. Allows unlimited values.</li> <li><strong style="font-weight: bold;">total_price</strong>: (commerce_price) Total price. The total price of the order.</li> <li><strong style="font-weight: bold;">total_paid</strong>: (commerce_price) Total paid. The total paid price of the order.</li> <li><strong style="font-weight: bold;">state</strong>: (state) State. The order state.</li> <li><strong style="font-weight: bold;">data</strong>: (map) Data. A serialized array of additional data.</li> <li><strong style="font-weight: bold;">locked</strong>: (boolean) Locked.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the order was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the order was last edited.</li> <li><strong style="font-weight: bold;">placed</strong>: (timestamp) Placed. The time when the order was placed.</li> <li><strong style="font-weight: bold;">completed</strong>: (timestamp) Completed. The time when the order was completed.</li> <li><strong style="font-weight: bold;">cart</strong>: (boolean) Cart.</li> <li><strong style="font-weight: bold;">checkout_flow</strong>: (entity_reference to commerce_checkout_flow) Checkout flow.</li> <li><strong style="font-weight: bold;">checkout_step</strong>: (string) Checkout step.</li> <li><strong style="font-weight: bold;">payment_gateway</strong>: (entity_reference to commerce_payment_gateway) Payment gateway. The payment gateway.</li> <li><strong style="font-weight: bold;">payment_method</strong>: (entity_reference to commerce_payment_method) Payment method. The payment method.</li> <li><strong style="font-weight: bold;">coupons</strong>: (entity_reference to commerce_promotion_coupon) Coupons. Coupons that have been applied to order. Allows unlimited values.</li> </ol> <h2 id="commerce_order_item" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Order item entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Order (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/order/src/Entity/OrderItem.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_order\Entity\OrderItem</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">order_item_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (entity_reference to commerce_order_item_type) Order item type.</li> <li><strong style="font-weight: bold;">order_id</strong>: (entity_reference to commerce_order) Order. The parent order.</li> <li><strong style="font-weight: bold;">purchased_entity</strong>: (entity_reference to node) Purchased entity. The purchased entity.</li> <li><strong style="font-weight: bold;">title</strong>: (string) Title. The order item title.</li> <li><strong style="font-weight: bold;">quantity</strong>: (decimal) Quantity. The number of purchased units.</li> <li><strong style="font-weight: bold;">unit_price</strong>: (commerce_price) Unit price. The price of a single unit.</li> <li><strong style="font-weight: bold;">overridden_unit_price</strong>: (boolean) Overridden unit price. Whether the unit price is overridden.</li> <li><strong style="font-weight: bold;">total_price</strong>: (commerce_price) Total price. The total price of the order item.</li> <li><strong style="font-weight: bold;">adjustments</strong>: (commerce_adjustment) Adjustments. Allows unlimited values.</li> <li><strong style="font-weight: bold;">uses_legacy_adjustments</strong>: (boolean) Uses legacy adjustments.</li> <li><strong style="font-weight: bold;">data</strong>: (map) Data. A serialized array of additional data.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the order item was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the order item was last edited.</li> </ol> <h2 id="commerce_payment_method" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Payment method entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Payment (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/payment/src/Entity/PaymentMethod.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_payment\Entity\PaymentMethod</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">method_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (string) Payment method type.</li> <li><strong style="font-weight: bold;">payment_gateway</strong>: (entity_reference to commerce_payment_gateway) Payment gateway. The payment gateway.</li> <li><strong style="font-weight: bold;">payment_gateway_mode</strong>: (string) Payment gateway mode. The payment gateway mode.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Owner. The payment method owner.</li> <li><strong style="font-weight: bold;">remote_id</strong>: (string) Remote ID. The payment method remote ID.</li> <li><strong style="font-weight: bold;">billing_profile</strong>: (entity_reference_revisions) Billing profile. Billing profile</li> <li><strong style="font-weight: bold;">reusable</strong>: (boolean) Reusable. Whether the payment method is reusable.</li> <li><strong style="font-weight: bold;">is_default</strong>: (boolean) Default. Whether this is the user's default payment method.</li> <li><strong style="font-weight: bold;">expires</strong>: (timestamp) Expires. The time when the payment method expires. 0 for never.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the payment method was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the payment method was last edited.</li> </ol> <h2 id="commerce_payment" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Payment entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Payment (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/payment/src/Entity/Payment.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_payment\Entity\Payment</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">payment_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (string) Payment type.</li> <li><strong style="font-weight: bold;">payment_gateway</strong>: (entity_reference to commerce_payment_gateway) Payment gateway. The payment gateway.</li> <li><strong style="font-weight: bold;">payment_gateway_mode</strong>: (string) Payment gateway mode. The payment gateway mode.</li> <li><strong style="font-weight: bold;">payment_method</strong>: (entity_reference to commerce_payment_method) Payment method. The payment method.</li> <li><strong style="font-weight: bold;">order_id</strong>: (entity_reference to commerce_order) Order. The parent order.</li> <li><strong style="font-weight: bold;">remote_id</strong>: (string) Remote ID. The remote payment ID.</li> <li><strong style="font-weight: bold;">remote_state</strong>: (string) Remote State. The remote payment state.</li> <li><strong style="font-weight: bold;">amount</strong>: (commerce_price) Amount. The payment amount.</li> <li><strong style="font-weight: bold;">refunded_amount</strong>: (commerce_price) Refunded amount. The refunded payment amount.</li> <li><strong style="font-weight: bold;">state</strong>: (state) State. The payment state.</li> <li><strong style="font-weight: bold;">authorized</strong>: (timestamp) Authorized. The time when the payment was authorized.</li> <li><strong style="font-weight: bold;">expires</strong>: (timestamp) Expires. The time when the payment expires. 0 for never.</li> <li><strong style="font-weight: bold;">completed</strong>: (timestamp) Completed. The time when the payment was completed.</li> <li><strong style="font-weight: bold;">test</strong>: (boolean) Test. Whether this is a test payment.</li> <li><strong style="font-weight: bold;">captured</strong>: (timestamp) Captured. The time when the payment was captured.</li> </ol> <h2 id="commerce_promotion" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Promotion entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Promotion (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/promotion/src/Entity/Promotion.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_promotion\Entity\Promotion</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">promotion_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">langcode</strong>: (language) Language.</li> <li><strong style="font-weight: bold;">name</strong>: (string) Name. The promotion name.</li> <li><strong style="font-weight: bold;">display_name</strong>: (string) Display name. If provided, shown on the order instead of "Discount".</li> <li><strong style="font-weight: bold;">description</strong>: (string_long) Description. Additional information about the promotion to show to the customer</li> <li><strong style="font-weight: bold;">order_types</strong>: (entity_reference to commerce_order_type) Order types. The order types for which the promotion is valid. Allows unlimited values.</li> <li><strong style="font-weight: bold;">stores</strong>: (entity_reference to commerce_store) Stores. The stores for which the promotion is valid. Allows unlimited values.</li> <li><strong style="font-weight: bold;">offer</strong>: (commerce_plugin_item:commerce_promotion_offer) Offer type.</li> <li><strong style="font-weight: bold;">conditions</strong>: (commerce_plugin_item:commerce_condition) Conditions. Allows unlimited values.</li> <li><strong style="font-weight: bold;">condition_operator</strong>: (list_string) Condition operator. The condition operator.</li> <li><strong style="font-weight: bold;">coupons</strong>: (entity_reference to commerce_promotion_coupon) Coupons. Coupons which allow promotion to be redeemed. Allows unlimited values.</li> <li><strong style="font-weight: bold;">usage_limit</strong>: (integer) Usage limit. The maximum number of times the promotion can be used. 0 for unlimited.</li> <li><strong style="font-weight: bold;">start_date</strong>: (datetime) Start date. The date the promotion becomes valid.</li> <li><strong style="font-weight: bold;">end_date</strong>: (datetime) End date. The date after which the promotion is invalid.</li> <li><strong style="font-weight: bold;">compatibility</strong>: (list_string) Compatibility with other promotions.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Status. Whether the promotion is enabled.</li> <li><strong style="font-weight: bold;">weight</strong>: (integer) Weight. The weight of this promotion in relation to others.</li> <li><strong style="font-weight: bold;">default_langcode</strong>: (boolean) Default translation. A flag indicating whether this is the default translation.</li> </ol> <h2 id="commerce_promotion_coupon" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Coupon entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Promotion (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/promotion/src/Entity/Coupon.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_promotion\Entity\Coupon</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">promotion_id</strong>: (entity_reference to commerce_promotion) Promotion. The parent promotion.</li> <li><strong style="font-weight: bold;">code</strong>: (string) Coupon code. The unique, machine-readable identifier for a coupon.</li> <li><strong style="font-weight: bold;">usage_limit</strong>: (integer) Usage limit. The maximum number of times the coupon can be used. 0 for unlimited.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Status. Whether the coupon is enabled.</li> </ol> <h2 id="commerce_log" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Log entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>: Commerce Log (part of<span> </span><a href="https://www.drupal.org/project/commerce" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">commerce module</a>)<br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/log/src/Entity/Log.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_log\Entity\Log</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">log_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) User. The user for the log.</li> <li><strong style="font-weight: bold;">template_id</strong>: (string) Log template ID. The log template plugin ID</li> <li><strong style="font-weight: bold;">category_id</strong>: (string) Log category ID. The log category plugin ID</li> <li><strong style="font-weight: bold;">source_entity_id</strong>: (integer) Source entity ID. The source entity ID</li> <li><strong style="font-weight: bold;">source_entity_type</strong>: (string) Source entity type. The source entity type</li> <li><strong style="font-weight: bold;">params</strong>: (map) Params. A serialized array of parameters for the log template.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the log was created.</li> </ol> <h2 id="commerce_pricelist" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Price list entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>:<span> </span><a href="https://www.drupal.org/project/commerce_pricelist" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Commerce Pricelist</a><br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce_pricelist/-/blob/8.x-2.x/src/Entity/PriceList.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_pricelist\Entity\PriceList</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (string) Price list bundle.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Owner. The user that owns this price list.</li> <li><strong style="font-weight: bold;">name</strong>: (string) Name. The name of the price list.</li> <li><strong style="font-weight: bold;">stores</strong>: (entity_reference to commerce_store) Stores. The stores for which the price list is valid. Allows unlimited values.</li> <li><strong style="font-weight: bold;">customer</strong>: (entity_reference to user) Customer. The customer for which the price list is valid.</li> <li><strong style="font-weight: bold;">customer_roles</strong>: (entity_reference to user_role) Customer roles. The customer roles for which the price list is valid. Allows unlimited values.</li> <li><strong style="font-weight: bold;">start_date</strong>: (datetime) Start date. The date the price list becomes valid.</li> <li><strong style="font-weight: bold;">end_date</strong>: (datetime) End date. The date after which the price list is invalid.</li> <li><strong style="font-weight: bold;">weight</strong>: (integer) Weight. The weight of this price list in relation to other price lists.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Status. Whether the price list is enabled.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the price list was last edited.</li> </ol> <h2 id="commerce_pricelist_item" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Price list item entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>:<span> </span><a href="https://www.drupal.org/project/commerce_pricelist" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Commerce Pricelist</a><br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce_pricelist/-/blob/8.x-2.x/src/Entity/PriceListItem.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_pricelist\Entity\PriceListItem</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (string) Price list item bundle.</li> <li><strong style="font-weight: bold;">uid</strong>: (entity_reference to user) Owner. The user that owns this price list item.</li> <li><strong style="font-weight: bold;">price_list_id</strong>: (entity_reference to commerce_pricelist) Price list. The parent price list.</li> <li><strong style="font-weight: bold;">purchasable_entity</strong>: (entity_reference to commerce_product_variation) Purchasable entity. The purchasable entity.</li> <li><strong style="font-weight: bold;">quantity</strong>: (decimal) Quantity. The quantity tier.</li> <li><strong style="font-weight: bold;">list_price</strong>: (commerce_price) List price. The list price.</li> <li><strong style="font-weight: bold;">price</strong>: (commerce_price) Price. The price.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Status. Whether the price list item is enabled.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the price list item was last edited.</li> </ol> <h2 id="commerce_shipment" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Shipment entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>:<span> </span><a href="https://www.drupal.org/project/commerce_shipping" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Shipping</a><br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce_shipping/-/blob/8.x-2.x/src/Entity/Shipment.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_shipping\Entity\Shipment</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">shipment_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">type</strong>: (entity_reference to commerce_shipment_type) Shipment type.</li> <li><strong style="font-weight: bold;">order_id</strong>: (entity_reference to commerce_order) Order. The parent order.</li> <li><strong style="font-weight: bold;">package_type</strong>: (string) Package type. The package type.</li> <li><strong style="font-weight: bold;">shipping_method</strong>: (entity_reference to commerce_shipping_method) Shipping method. The shipping method</li> <li><strong style="font-weight: bold;">shipping_service</strong>: (string) Shipping service. The shipping service.</li> <li><strong style="font-weight: bold;">shipping_profile</strong>: (entity_reference_revisions) Shipping information.</li> <li><strong style="font-weight: bold;">title</strong>: (string) Title. The shipment title.</li> <li><strong style="font-weight: bold;">items</strong>: (commerce_shipment_item) Items. Allows unlimited values.</li> <li><strong style="font-weight: bold;">weight</strong>: (physical_measurement) Weight.</li> <li><strong style="font-weight: bold;">original_amount</strong>: (commerce_price) Original amount. The original amount.</li> <li><strong style="font-weight: bold;">amount</strong>: (commerce_price) Amount. The amount.</li> <li><strong style="font-weight: bold;">adjustments</strong>: (commerce_adjustment) Adjustments. Allows unlimited values.</li> <li><strong style="font-weight: bold;">tracking_code</strong>: (string) Tracking code. The shipment tracking code.</li> <li><strong style="font-weight: bold;">state</strong>: (state) State. The shipment state.</li> <li><strong style="font-weight: bold;">data</strong>: (map) Data. A serialized array of additional data.</li> <li><strong style="font-weight: bold;">created</strong>: (created) Created. The time when the shipment was created.</li> <li><strong style="font-weight: bold;">changed</strong>: (changed) Changed. The time when the shipment was last updated.</li> <li><strong style="font-weight: bold;">shipped</strong>: (timestamp) Shipped. The time when the shipment was shipped.</li> </ol> <h2 id="commerce_shipping_method" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Shipping method entity</h2> <p style="margin: 0px 0px 1.2em;"><strong style="font-weight: bold;">Module</strong>:<span> </span><a href="https://www.drupal.org/project/commerce_shipping" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Shipping</a><br /> <strong style="font-weight: bold;">Class</strong>:<span> </span><a href="https://git.drupalcode.org/project/commerce_shipping/-/blob/8.x-2.x/src/Entity/ShippingMethod.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">Drupal\commerce_shipping\Entity\ShippingMethod</a></p> <p style="margin: 0px 0px 1.2em;">List of base field definitions:</p> <ol style="margin: 1em 0px; padding: 0px 0px 0.25em 15px;"> <li><strong style="font-weight: bold;">shipping_method_id</strong>: (integer) ID.</li> <li><strong style="font-weight: bold;">uuid</strong>: (uuid) UUID.</li> <li><strong style="font-weight: bold;">langcode</strong>: (language) Language.</li> <li><strong style="font-weight: bold;">stores</strong>: (entity_reference to commerce_store) Stores. The stores for which the shipping method is valid. Allows unlimited values.</li> <li><strong style="font-weight: bold;">plugin</strong>: (commerce_plugin_item:commerce_shipping_method) Plugin.</li> <li><strong style="font-weight: bold;">name</strong>: (string) Name. The shipping method name.</li> <li><strong style="font-weight: bold;">conditions</strong>: (commerce_plugin_item:commerce_condition) Conditions. Allows unlimited values.</li> <li><strong style="font-weight: bold;">condition_operator</strong>: (list_string) Condition operator. The condition operator.</li> <li><strong style="font-weight: bold;">weight</strong>: (integer) Weight. The weight of this shipping method in relation to others.</li> <li><strong style="font-weight: bold;">status</strong>: (boolean) Enabled. Whether the shipping method is enabled.</li> <li><strong style="font-weight: bold;">default_langcode</strong>: (boolean) Default translation. A flag indicating whether this is the default translation.</li> </ol> <h2 id="contrib" style="margin: 1em 0px 2px; font-size: 1.429em; font-weight: inherit; line-height: 1.4;">Available properties for other content entities</h2> <p style="margin: 0px 0px 1.2em;">This reference includes Drupal Commerce content entities and some provided by related contributed modules. The previous article included a<span> </span><a href="/blog/drupal-migrations-reference-list-properties-content-entity" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">reference for Drupal core content entities</a>. That being said, it would be impractical to cover all contributed modules. To get a list of yourself for other content entities, load the <code><a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/lib/Drupal/Core/Entity/EntityFieldManager.php" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">entity_type.manager</a></code> service and call its <code><a href="https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/lib/Drupal/Core/Entity/EntityFieldManager.php#L430" style="background-color: transparent; text-decoration: none; border-bottom: 1px dotted; color: rgb(0, 113, 179);">getFieldStorageDefinitions()</a></code> method passing the machine name of the entity as a parameter. Although this reference only covers content entities, the same process can be used for configuration entities.</p> <p style="margin: 0px 0px 1.2em;">What did you learn in today’s article? Did you know that there were so many entity properties provided by Drupal Commerce? Were you aware that the list of available properties depend on factors like if the entity is fieldable, translatable, and revisionable? Did you know how to find properties for content entities from contributed modules? Please share your answers in the comments. Also, we would be grateful if you shared this article with your friends and colleagues.</p> </div> </div> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> </article> </div> </div> </div> , <div class="flow_middle"> <p><em>Sign up if you want to know when Mauricio and Agaric give a migration training:</em></p> </div> , <div class="flow_middle"> <form class="webform-submission-form webform-submission-add-form webform-submission-migrations-form webform-submission-migrations-add-form webform-submission-migrations-paragraph-535-form webform-submission-migrations-paragraph-535-add-form js-webform-details-toggle webform-details-toggle antibot" data-drupal-selector="webform-submission-migrations-paragraph-535-add-form" data-action="/blog/feed" action="/antibot" method="post" id="webform-submission-migrations-paragraph-535-add-form" accept-charset="UTF-8"> <noscript> <div class="antibot-no-js antibot-message antibot-message-warning">You must have JavaScript enabled to use this form.</div> </noscript> <div class="js-form-item form-item js-form-type-email form-item-your-email js-form-item-your-email"> <label for="edit-your-email" class="form-label label control-label js-form-required form-required">Your e-mail address</label> <div class="control"> <input data-drupal-selector="edit-your-email" type="email" id="edit-your-email" name="your_email" value="" size="60" maxlength="254" class="form-email required input" required="required" aria-required="true" /> </div> </div> <div class="js-form-item form-item js-form-type-textfield form-item-your-name js-form-item-your-name"> <label for="edit-your-name" class="form-label label control-label">Your name (optional)</label> <div class="control"> <input data-drupal-selector="edit-your-name" type="text" id="edit-your-name" name="your_name" value="" size="60" maxlength="255" class="form-text input" /> </div> </div> <div data-drupal-selector="edit-actions" class="form-actions webform-actions field js-form-wrapper form-wrapper" id="edit-actions"> <div class="control"> <button class="webform-button--submit button button--primary js-form-submit form-submit is-primary" data-drupal-selector="edit-actions-submit" type="submit" id="edit-actions-submit" name="op" value="Get notified"> <span>Get notified</span> </button> </div> </div> <input autocomplete="off" data-drupal-selector="form-vqdibsj57ebyasu7bp65cptdjwsujju6vjmghr-d-k0" type="hidden" name="form_build_id" value="form-vqDiBSj57eBYASU7bp65cpTDjWSujjU6vJmgHR-d_K0" /> <input data-drupal-selector="edit-webform-submission-migrations-paragraph-535-add-form" type="hidden" name="form_id" value="webform_submission_migrations_paragraph_535_add_form" /> <input data-drupal-selector="edit-antibot-key" type="hidden" name="antibot_key" value="" /> </form> </div> 2020 May 04 Mauricio Dinarte https://agaric.coop/Drupal%20migrations%20reference%3A%20List%20of%20properties%20per%20Commerce%20content%20entity Going online together to save our lives https://agaric.coop/ <div class="flow_middle"> <p>Anthropologist Kasey Qynn Dolin noted, of our societal moment that <abbr title="COronaVIrus Disease which emerged in 2019">COVID-19</abbr> has thrown into sharp relief:</p> <blockquote> <p>We expected going online would lead to mass democratization and access to information that empowers large groups of people.</p> <p>Instead, we got outsourced.</p> </blockquote> <p>Online connectivity gave people who own means of production the ability to outsource work in a way that offloads expenses— the cost of the things which generate the conditions for the wealthy and powerful to profit in the first place. Some of this, like shifting the expenses of maintaining fleets of vehicles off of a corporation's balance sheet, have been celebrated as technology-enabled innovations in business models. This is usually a big charade, a technological gloss on what is just another, relatively minor, attempt to concentrate wealth and power. Meanwhile, the biggest return on capital and the biggest harmful effects on people come from practices that business media don't focus on.</p> <p>The world going online has helped the owning class shift the costs of workers simply living (health care, child care, retirement, sustenance or more-than-sustenance wages) onto people, communities, and whole nations less able to hold global corporations to account.</p> <p>There are a couple big reasons this happened.</p> <p>First, technology is not neutral, and it tends to serve the needs of those who shape it.</p> <p>Even with the incredible historical happenstance of US military money flowing to academic researchers who developed and incubated an Internet that was computer-to-computer rather than hierarchical…</p> <p>…and even with the impressive tradition of technologists making decisions openly and favoring adopting standards by rough consensus that allow unaffiliated parts to work with one another…</p> <p>…the services built on the Web and other Internet-based protocols and standards reflect the desire for control (for wealth and for power) held by corporations, governments, and venture capitalists— because these are the entities that have been spending the money and increasingly calling the shots for the past twenty years.</p> <p>Second, the same reason a miracle was required to make the Internet distributed in the first place has worked against many of the technologies built on the Internet being peer-to-peer— it is hard to come to agreement on protocols and hard to make them easy to use. It is easier to build software and services which are ultimately within one organization's control, and the organizations with the resources to do even this have tended to be the same ones centralizing power (with notable exceptions such as Wikipedia and the Internet Archive).</p> <p>Similarly to networked computers, in the world of organized humans it is very hard to develop ways to build shared power. It is much easier to use communications technology to move work and consequences farther from those who already hold centralized power.</p> <p>However, these facts working against us don't mean we have to take our struggle for our human rights and for justice entirely offline. And struggle we must, because the people who hold wealth and power now are seeing, in this health and economic crisis, just how much can be denied us without threatening their wealth and power.</p> <p>A lot of the technology developed for business as usual, even the proprietary stuff we don't actually control like Slack and Google Docs, can be put to use by movements for justice and liberty. There are a lot of similarities in tools people need in order to work together, whether in companies or in community groups. That is not to say that the movement to build truly free as in freedom alternatives to these is not good and important. Indeed, the development of free libre open source software is downright great and vital when led by or done in direct collaboration with groups marginalized by capitalism that do have different needs, such as a threat model that must take into account government repression and fascist threats.</p> <p>But we need to concentrate our finite time on building technology corporate and political masters would never want built.</p> <p>We need to focus on that which builds shared power. <strong>We need to build ways to share power.</strong></p> <p>Power is organization. People organized in large groups can hold even global corporations to account. We can replace existing structures, as proves needed, to gain the sustenance needed for life; to gain the justice of a common share of all that nature has given and all that humanity has worked for; and to gain the freedom to do what we choose with it all.</p> <p>Any group with that level of power is unlikely to keep true to universal human rights and and all the rest unless power stays distributed within it— ideally throughout all society.</p> <p>Formal democracy, however flawed or perfected, has never been enough and will never be enough.</p> <p>We need ways of communicating and making decisions that work well and keep power distributed.</p> <p>Sharing the power which is inherent in controlling communication and in making decisions requires sharing the work of both.</p> <p>There's endless creativity required in getting people to see a better world is possible, and infinite strategy needed to achieve it. Thinking about how to share power while building power is necessary, but it is too much to have to figure out from scratch while doing everything else. It is as unrealistic to expect every group of people who give a damn to craft tech for mass democracy and distributed power as it is to expect every group of people to make their own word processing and web publishing software.</p> <p>Which is why we need to build online tools for sharing the power that we achieve by organizing.</p> <p>One indispensable piece, unsolved in practice but in theory solvable technically, is <strong>distributing the power of coordinated messaging</strong>. Coordinated messaging, in turn, is the root of both collective decision-making and coordinating action.</p> <p>We need to build up this power to coordinate and communicate while being confident we will be able to use this power collectively. Here is a way that could work:</p> <p>A person who is part of a movement has a piece of information they think every person in the movement should know, or that they think should be spread as widely as possible to the public. They have to go through some process that is the same for everybody, such as getting any two people in the movement to approve the content and crafting of the message. Then the proposed communication goes not to a standing committee but to a small group of people randomly drawn from the movement and asked to decide if it's something everybody should see now, or not.</p> <p>That's the technology I want to build to make us all going online finally lead to mass democratization and power for all people over their own lives.</p> <p>Join me.</p> </div> , <div class="flow_middle"> <h3>Hear from us</h3> </div> , <div class="flow_middle"> <form class="webform-submission-form webform-submission-add-form webform-submission-join-the-movement-form webform-submission-join-the-movement-add-form webform-submission-join-the-movement-paragraph-529-form webform-submission-join-the-movement-paragraph-529-add-form js-webform-details-toggle webform-details-toggle antibot" data-drupal-selector="webform-submission-join-the-movement-paragraph-529-add-form" data-action="/blog/feed" action="/antibot" method="post" id="webform-submission-join-the-movement-paragraph-529-add-form" accept-charset="UTF-8"> <noscript> <div class="antibot-no-js antibot-message antibot-message-warning">You must have JavaScript enabled to use this form.</div> </noscript> <div id="edit-intro-text" class="js-form-item form-item js-form-type-processed-text form-item- js-form-item- form-no-label"> <p>We will send you very occasional dispatches from our perspective on various overlapping movements for cooperation, freedom and justice as workers and as passionate observers.</p> </div> <div class="js-form-item form-item js-form-type-email form-item-email js-form-item-email"> <label for="edit-email" class="form-label label control-label">Your e-mail address</label> <div class="control"> <input data-drupal-selector="edit-email" type="email" id="edit-email" name="email" value="" size="60" maxlength="254" class="form-email input" /> </div> </div> <div data-drupal-selector="edit-actions" class="form-actions webform-actions field js-form-wrapper form-wrapper" id="edit-actions--3"> <div class="control"> <button class="webform-button--submit button button--primary js-form-submit form-submit is-primary" data-drupal-selector="edit-actions-submit-2" type="submit" id="edit-actions-submit--2" name="op" value="Join the movement"> <span>Join the movement</span> </button> </div> </div> <input autocomplete="off" data-drupal-selector="form-v11csxkfjsr0qhkuezy2ogbgggqm73gyfpbxk24unjk" type="hidden" name="form_build_id" value="form-v11cSXkFJSR0QHKUEZy2OGBgGGqM73gYFpBxk24uNjk" /> <input data-drupal-selector="edit-webform-submission-join-the-movement-paragraph-529-add-form" type="hidden" name="form_id" value="webform_submission_join_the_movement_paragraph_529_add_form" /> <input data-drupal-selector="edit-antibot-key" type="hidden" name="antibot_key" value="" /> </form> </div> 2020 April 21 Benjamin Melançon https://agaric.coop/Going%20online%20together%20to%20save%20our%20lives The Find It Platform - An Open-Source Program Locator for Communities https://agaric.coop/ <section data-headerbg="transparent" class="hero is-cinnamon hero-project is-large"> <div class="container"> <div class="hero-image"> <img src="/sites/default/files/styles/max_650x650/public/title-image/2020-04/Screenshot_2020-04-04%20Home%20Opportunities%20for%20those%20who%20live%2C%20work%2C%20and%20play%20in%20Cambridge%2C%20Massachusetts%20Find%20It%20Cambridge.jpg?itok=qf0RyUez" width="481" height="650" alt="Screenshot of Find It Cambridge on mobile." loading="lazy" typeof="foaf:Image" /> </div> <div class="hero-body"> <h1 class="title">The Find It Platform </h1> <h2 class="subtitle">An Open-Source Program and Event Locator for Communities </h2> </div> </div> </section> , <div class="flow_middle"> <p>We partnered with the City of Cambridge to redesign Find It Cambridge, an online opportunity locator serving city residents, in a unique way— designing and developing out in the open, and releasing the software under an open-source license so that other cities can spin up their own Find It platforms.</p> <p>Cambridge, like many cities, has a wide array of programs and events happening to serve residents. However, it can be difficult for people to find and compare the many offerings out there. The city website has a calendar of events and list of departments. These are limited to government-run programs, though. There are myriad nonprofits and community groups that run programs and events that go unlisted. Social media platforms like Facebook have filled the gap in some areas, but promotion of these opportunities relies in many ways on people's social connections— leaving those most in need of services out of the loop. Find It Cambridge solves this problem by aggregating the many different opportunities happening into one website.</p> <p>The upgrade and <a href="https://agaric.coop/migration-trainings">migration</a> from Drupal 7 to Drupal 8 (ready for Drupal 9!) afforded Cambridge and Agaric the opportunity to make the directory of organizations, events, and programs better for families and their children and all involved, grounded in research and testing.</p> <h2 id="intuitive-structured-authoring-experience-for-service-providers">Intuitive, Structured Authoring Experience for Service Providers</h2> <p>The value of a directory are the listings within it and it's truly a community effort to assemble enough accurate and up-to-date resources in a single place, for it to be useful. For Cambridge, the service providers that work at government agencies and nonprofits are the lifeblood of the directory. Without them, there would be no Find It Cambridge.</p> <p>The challenge then, is building a system that is easy enough for people (many already pressed for time) to take the time to enter their information into, while structuring the data to be easily searched and filtered on.</p> <p>Through user research, we mapped the information architecture to the mental models that service providers hold for their events and programs.</p> <h3 id="working-tabs">Input Fields Grouped by Key Questions</h3> <p>Most service providers thought of their events in terms of what the event is about, how to contact the organizers, who it is for, when it is happening, where it is happening, how much it costs, and if there is any sort of registration required. So we organized fields into working tabs to match: About, Contact, For whom, When, Where, Cost, and Signup.</p> <h3 id="autosave-and-soft-required-fields">Autosave and Soft Required Fields</h3> <p>Even with fields grouped by tabs, the form can take some time to complete. That's why we introduced autosave and soft save features. When working on a form, the site automatically saves the current draft every few seconds. Service providers can also save a draft to return to later. Fields that are required for publishing, are optional for a draft.</p> <h3 id="draft-states-to-save-work-for-later">Draft States to Save Work for Later</h3> <p>Service providers have many responsibilities to juggle. It's important that they can start creating an event or program, save it and return to it later before publishing it.</p> <p>Drupal has powerful workflow states, which we've put to use to help service providers clearly know the status of their content.</p> <p>A service provider can either save their content as a draft or publish it immediately. If saved as a draft, a banner appears on the page clearly indicating that the event or program is not yet published.</p> <p><figure role="group" class="align-center"> <img alt="Screencast of save as a draft workflow." data-entity-type="file" data-entity-uuid="13d6fd54-3de5-4fbe-8568-f95c4ae646cb" src="/sites/default/files/inline-images/find-it-program-draft.gif" width="640" height="282" loading="lazy" /> <figcaption>Authors can save their work as a draft, bypassing required fields until they're ready to publish.</figcaption> </figure> </p> <p>Authors can also create a draft alongside a published version. This allows new versions to be worked on, while maintaining the current page for site visitors.</p> <p><figure role="group" class="align-center"> <img alt="Screencast of workflow for having a draft while a previous version of a page stays publishe" data-entity-type="file" data-entity-uuid="de8a9c96-b54a-4458-9387-c701103ff644" src="/sites/default/files/inline-images/find-it-update-draft.gif" width="640" height="238" loading="lazy" /> <figcaption>Authors can have a published version of a page and also have a working draft that eventually becomes the new version.</figcaption> </figure> </p> <h3 id="help-text-and-character-counts-for-guidance">Help text and character counts for guidance</h3> <p>There are particular ways to write and format content on events and programs to make the most of Find It's features. We provide help text along the way to clue providers in on the best ways to write their content. We also include a character count so providers know if they're staying within the recommended limits for certain text fields.</p> <p><iframe allowfullscreen="" frameborder="0" height="260" sandbox="allow-same-origin allow-scripts" src="https://peertube.video/videos/embed/f22e3ee1-f5b6-4b86-bfa0-de82f853ebb9" style="height: 260px" width="560"></iframe></p> <h3 id="bulk-select-for-quick-data-entry">Bulk Select for Quick Data Entry</h3> <p>Certain fields have many options. In some cases the majority of them apply. For example, many educational events are for all ages up to 18. In that scenario, having a "Select All" option speeds up the data entry process. The Selectize JavaScript library adds elegant toggle and check all options to multivalue fields. We created the <a href="https://www.drupal.org/project/checkboxesjs">CheckboxesJS Drupal project</a> so that other Drupal sites can easily incorporate these features on fields of their choosing.</p> <p><iframe allowfullscreen="" frameborder="0" height="350" sandbox="allow-same-origin allow-scripts" src="https://peertube.video/videos/embed/4da8e97f-1005-46f3-95fe-540e1a4f203b" style="height: 350px" width="560"></iframe></p> <h3 id="conditional-fields-to-show-only-what-is-relevant">Conditional Fields to Show Only What is Relevant</h3> <p>Some fields on Event and Programs only need to show under certain conditions. For example, if an event doesn't require registration, then there's no need to worry service providers with a registration link field. Using conditional logic keeps forms simple and streamlined.</p> <p><iframe allowfullscreen="" frameborder="0" height="350" sandbox="allow-same-origin allow-scripts" src="https://peertube.video/videos/embed/099ab061-862c-4cc2-98f8-45678b081785" style="height: 350px" width="560"></iframe></p> <h3 id="multiple-dates">Multiple Dates</h3> <p>There was a lot of discussion on whether to support repeating rules or instead allow multiple dates. We decided on multiple dates as experience has shown that even repeating events oftentimes have exceptions (and because the events we import from Cambridge Public Libraries are a list of arbitrary dates rather than a recurring rule, and somehow <a href="https://stackoverflow.com/questions/58103576/how-to-work-with-rrule-datetime-series-and-arbitrary-non-rrule-datetime-series/58110455">no one in computer science has created a library to produce a best-effort recurring rule from a list of dates</a>).</p> <p><figure role="group" class="align-center"> <img alt="Multiple date fields." data-entity-type="file" data-entity-uuid="41c91203-d9b5-4f6a-b8e0-db9ef5d5cac1" src="/sites/default/files/inline-images/find-it-multiple-dates.png" width="1339" height="685" loading="lazy" /> <figcaption>Multiple date fields allow for flexibility on events and programs that happen more than once.</figcaption> </figure> </p> <h2 id="easy-but-powerful-search-for-residents">Easy but Powerful Search for Residents</h2> <p>Find It search is powered by Apache Solr, a popular open-source enterprise search platform. We use its numerous features to make the search results as relevant as possible for site visitors.  It's an ongoing process of tweaks; here are some of the things we've done so far.</p> <h3 id="weighted-fields-for-relevance">Weighted Fields for Relevance</h3> <p>On content with lots of data like the events and programs of Find It, certain fields carry more importance than others. The title of an event, for example, is one of the most important. The transporation notes, on the other hand, carries less significance in search queries. When someone types the keyword "music lesson", an event with music lesson in the title or summary shows up before a program for English lessons.</p> <h3 id="synonym-matching">Synonym Matching</h3> <p>When someone searches "childcare" but a program uses "child care", the search engine should know these are equivalent. The same is true for "STEM" and "science education."</p> <p>Find It supports synonyms. The site manager can define synonyms so that when site visitors search for a certain term, results with matching synonyms show up as well.</p> <h3 id="key-information-in-search-results">Key Information in Search Results</h3> <p>We used the results of our user research to show the critical information people need to pin point the right opportunities: title, neighborhood, and a short summary.</p> <h3 id="filters-for-sophisticated-queries">Filters for Sophisticated Queries</h3> <p>Filters help users narrow a search query down to specific criteria. In our testing, we found that age and neighborhood were most important, especially for low-income caregivers. For those of us that rely on public transportation, events and programs need to be nearby. We placed these filters accordingly towards the top of the page.</p> <p>Naming conventions in Cambridge are unique, which is true for other cities too. Residents might not know the official name of their neighborhood or live at the border between two. We've included a labeled, clickable map to help users choose the right neighborhood. We built this so that other Find It platforms can upload their own SVG map to show their neighborhood.</p> <p><iframe allowfullscreen="" frameborder="0" height="315" sandbox="allow-same-origin allow-scripts" src="https://peertube.video/videos/embed/c1a873ae-f54c-43c7-b922-2289cf06f667" style="height: 315px" width="560"></iframe></p> <h2 id="informative-opportunity-pages">Informative Opportunity Pages</h2> <p>Find It comes out of the box with four different types of opportunities: Events, Places, Organizations and Programs.</p> <h3 id="organization">Organization</h3> <p>The organization serves as the foundation for opportunities posted on a Find It page. Every event and program posted to Find It, belongs to an organization. This helps an organization's page serve as a mini-website. When an event or program is published, it automatically shows up on its organization page.</p> <p>Organizations can also have "child" organizations, which is helpful for larger groups that might have distinct sub-committees or departments that have sub-departments.</p> <p><figure role="group" class="align-center"> <img alt="Related programs field." data-entity-type="file" data-entity-uuid="6ca76973-573a-4089-b358-4d22a96e06ee" src="/sites/default/files/inline-images/find-it-parent-organizations.png" width="796" height="457" loading="lazy" /> <figcaption>An organization can have a parent - child relationship.</figcaption> </figure> </p> <h3 id="event">Event</h3> <p>An event is an opportunity with a clear start and end date. When an event is published it shows up on the Homepage, Events page, Search page and on the organization's page.</p> <p>Visitors can sort opportunities by start date to find upcoming events.</p> <p><figure role="group" class="align-center"> <img alt="Find It event page." data-entity-type="file" data-entity-uuid="a6b68c45-c1e9-4f09-bc51-d876832a4b42" src="/sites/default/files/inline-images/find-it-event.png" width="1920" height="2568" loading="lazy" /> <figcaption>An event's multiple dates is converted into human friendly language.</figcaption> </figure> </p> <h3 id="program">Program</h3> <p>A program is similar to an event. In fact, most fields are shared between the two. A program though, implies more longevity and commitment than an event. Rather than requiring a specific date or dates, a program can simply be "ongoing." There is the option to include specific dates though.</p> <p><img alt="Find It program page." data-entity-type="file" data-entity-uuid="7489f309-d3f3-43c2-9221-b9de428480a1" src="/sites/default/files/inline-images/find-it-program.png" class="align-center" width="1920" height="3390" loading="lazy" /></p> <h3 id="place">Place</h3> <p>In the first version of Find It Cambridge, a new opportunity surfaced that didn't quite fit into the event, program, or organization categories. Parks, neighborhood pools, and other destinations were a good fit for Find It's library of opportunities. They have open hours, but many of the event fields were irrelevant. The same went for Programs. In fact, sometimes these places have events or programs happening <em>at</em> them.</p> <p>These are community-minded destinations people can go to. In other words, places.</p> <p><img alt="Find It place page." data-entity-type="file" data-entity-uuid="99be455e-d147-40e8-af99-e2e2d1fc1f6f" src="/sites/default/files/inline-images/find-it-place.png" class="align-center" width="1920" height="1624" loading="lazy" /></p> <h2 id="bring-find-it-to-your-city">Bring Find It to Your City!</h2> <p>Find It is helping Cambridge residents connect with activities and services to improve their lives. We would love to help do the same for other cities, counties, and other communities. The platform is open-source and flexible so that communities can customize it to their needs.</p> <p>Whether you are city IT staff, a developer that works with cities, or are a resident that could use a Find It in your community, we'd love to talk.</p> <p> </p> </div> , <div class="flow_middle"> <form class="webform-submission-form webform-submission-add-form webform-submission-find-it-platform-form webform-submission-find-it-platform-add-form webform-submission-find-it-platform-paragraph-527-form webform-submission-find-it-platform-paragraph-527-add-form js-webform-details-toggle webform-details-toggle antibot" data-drupal-selector="webform-submission-find-it-platform-paragraph-527-add-form" data-action="/blog/feed" action="/antibot" method="post" id="webform-submission-find-it-platform-paragraph-527-add-form" accept-charset="UTF-8"> <noscript> <div class="antibot-no-js antibot-message antibot-message-warning">You must have JavaScript enabled to use this form.</div> </noscript> <div class="js-form-item form-item js-form-type-textfield form-item-name js-form-item-name"> <label for="edit-name" class="form-label label control-label">Name</label> <div class="control"> <input data-drupal-selector="edit-name" type="text" id="edit-name" name="name" value="" size="60" maxlength="255" class="form-text input" /> </div> </div> <div class="js-form-item form-item js-form-type-email form-item-email js-form-item-email"> <label for="edit-email--2" class="form-label label control-label">Email</label> <div class="control"> <input data-drupal-selector="edit-email" type="email" id="edit-email--2" name="email" value="" size="60" maxlength="254" class="form-email input" /> </div> </div> <input autocomplete="off" data-drupal-selector="form-7i3fikgxxum9x7hkopxzyth7a0y5-v3jvsmwqx7utbs" type="hidden" name="form_build_id" value="form-7i3fIKgXXUm9X7HKOpXZYth7A0Y5_v3jvSmWqX7Utbs" /> <input data-drupal-selector="edit-webform-submission-find-it-platform-paragraph-527-add-form" type="hidden" name="form_id" value="webform_submission_find_it_platform_paragraph_527_add_form" /> <input data-drupal-selector="edit-antibot-key" type="hidden" name="antibot_key" value="" /> <div class="field is-grouped form-actions js-form-wrapper form-wrapper" data-drupal-selector="edit-actions" id="edit-actions--5"> <div class="control"> <button class="webform-button--submit button button--primary js-form-submit form-submit is-primary" data-drupal-selector="edit-submit" type="submit" id="edit-submit--3" name="op" value="Submit"> <span>Submit</span> </button> </div> </div> </form> </div> 2020 April 21 Clayton Dewey, Benjamin Melançon https://agaric.coop/The%20Find%20It%20Platform%20-%20An%20Open-Source%20Program%20Locator%20for%20Communities