 Okay. Thank you all for coming. My session is, when good code is not enough, lessons learned, maintaining modules. Obviously, this is going to be a share of my experience maintaining a lot of Drupal sites, and particularly how I find this different to maintaining open source projects. So, for those of you that don't know me yet, my name is Arlina Espinosa. I work for Chapter 3. I've been with Chapter 3 around nine years now, I think. I've been doing Drupal since Drupal 6. I've lost dates by this time, so probably over 10 years or so. And I still love Drupal, so that's something tells me when I'm here. And a pandemic happened and I'm just getting back to stuff doing sessions, so I'm a little bit rusty, which has for that. I was also able to have my full night's sleep after four years after two kids, so I've just been sleeping for three weeks, so bear with me. So, overall over most of my time doing Drupal, obviously most of the time I've been doing client work. And client work means you have a site. You're building a site for a client. There's some business requirements that you got to fulfill. You talk with the client, you start building, you iterate, you show something, you iterate again, etc. By doing client size, obviously you start working on contrib, you find something that doesn't work right, it has bugs, you want to contribute, etc. You submit an issue, a patch, etc. I've also maintained a couple of Drupal modules, but in that experience has been like, I did this for this client and I think it's good enough to put out there and if someone else wants to keep using it, I'll provide some support, but it's the functionality that we built for this site that can be reused. But I think my greatest open source experience was when Chapter 3 was working with the Google Apigee team. The Apigee, if you guys are familiar with Apigee, they do API management. We weren't working on that part, obviously that's Google's functionality. We were helping them build their developer portals offering. Apigee has some integrated portals that offer, you know, if you're using Apigee to manage your APIs, you want to have a developer portal for documentation documenting those APIs. They have internal portals which provide some default functionality, but you know, either like them or not. And if you didn't like that functionality, you could use Drupal, a product called Drupal developer portal kickstart to create your own developer portal using Drupal, your own business requirements, right? And developer portal kickstart was Drupal distribution. It was supported by several Drupal modules, Apigee Edge, the main connection to Apigee, Apigee monetization, API catalog to support documentation specs, et cetera. And obviously the PHP client library that connected everything. So we worked with them for about two years. Got some friends in here that helped with that as well. And it was a really interesting project. It was a whole different beast of, you know, working on a site versus actually maintaining a project. And you might wonder how that is. When you're working on a site, there's some things that you really know. Should I present this? Sorry. As I said, I'm rusty. So when you're working on a site, there's several things that you know. You know what data is on the side, you know the notes, the content, the web forms. You know the configuration each module has, what you're using it, how you're using it, what you're using it for. You know your database schemas, the tables, anything that's there, you can actually like pick in and see. So developers and times we do stuff like houses working, et cetera. So you know what modules are installed? And most importantly, you know the business logic that's driving that website. What is this supposed to do? So you have a very clear idea of a picture of what you want to build and how to get there. You have control of a lot of things, in particular your environment. At least you can select features of your environment like memory, PHP version, you know all of those, the stack you're using, et cetera. Deployments, you can definitely schedule those when it's convenient for you. You might even do some trickery around, you know, I need to create a custom block, which is a content entity, but the block's going to be related in layout, so it's configuration. So I'm going to push this deployment, then I'm going to create the block on production, then I pull down the database, export configuration, and then do the whole thing again. You know you do some trickery like that because you have control of the whole stack, the whole deployment, your whole repo. And you also have control, or at least you get to know your contributors, who's there, the company might implement some policy, you know, over deployment processes, some code styling, et cetera. So all of that are knowns, and you have control over. When you're maintaining a project, you have almost no control over any of those. What you got is some functionality specifications, and those drive some user stories, right? So you got your specifications, this module is supposed to connect to Apigee, it's supposed to allow an interface for users to create an app, and that app is going to get the API, you know, a key and a secret to use some APIs, et cetera. You create your user stories, you break those down into user interfaces, this form, this screen, et cetera, and that's essentially what you have to work on. So as I mentioned, this is more of a shared out of experience of, I should have done it some different way, and especially things that I noticed that I would have done in a different way, because I also maintain a lot of client size, and I would have done it that way. So number one, when you're running updates, database updates, you know, you have your install file and you would run some hook updates in there. You don't know when someone is going to pull down updates for your module, and when they're going to run it, it might be that, you know, they update every month and they're very consistent, that's not reality, right? They're more likely going to wait a year and some developer is going to do a composer update of the whole thing and just pull everything down and run 20, 30 updates at the same time. So you don't know the state of any other module that's installed in there as well. Everything is unknown, especially you can't even make an assumption that your hook updates are going to run sequentially, because another module could implement that hook, hook update dependencies, and it can be run intermittently when you're not not sequentially. And you don't know the environment your update will run in. So it might have very little memory, etc. Again, you have no control over those things. So being aware of that, those things, please never remember update functions, not that I did it, but I have seen it done on client sites, and it can actually happen just by, you know, not merging a PR correctly to developers working on the same, you know, PR, they just like to think and it happens. But it's very messy, obviously, for someone downloading your module. Also, be very mindful of refactoring the update functions, because, you know, people might have already downloaded that. I do have a case where I did refactor an update because it was no longer needed if you applied update B, A, B, and C. But, you know, looking back from experience, that was not the right choice, because we did get a lot of support issues around that. So just deal with it and you got to implement the fix and have the fix for everyone. So there you have it. Another one would be if you're calling, you know, if you're creating a new field in Drupal or something like that, you will have your entity definition update manager doing all those calls to create fields, etc. Never call apply updates within your update function, because that would apply updates not just for your stuff, but for everything. So it's just knowing those APIs, how they work. Also, please never reveal the router. You know, it's just that that's different when you're running, you know, modules and when you're running an update function. You will destroy the router and Drupal needs to refresh itself when it's running updates. You don't have to run it with the man line or the UI. So please don't reveal the router. And this is a very interesting one. The last one, don't assume database schemas, which is the database structures. And essentially, don't call your hook schema. And I have myself done this several times with client sites. And with client sites, it's not so noticeable. Because again, you know, when you push the code, you deploy, you know, production is at this state, and then you deploy another update and you update your database schema. And you know, you have control over that. Another developer might scream at you that, hey, it's not working on my local pull down database production and everything would work, right? Let me show you this example. This goes back to Drupal 6. Even then there was this big warning in here, which we never read documentation on Drupal. Let's figure out this very brief example. You define your modules table in here. It's a table T, it's got two fields. And then you write an update function to create that table. So, you know, why, why repeat this array in here, you just call Drupal get schema for my module and create that table, right? Just call create that table. Then for the next release, you decide you don't need field B. So you update your schema hook. So it's just the fields that you need, you remove field B. And then you run another update hook, where you drop the field. And if someone, you know, installed this update, and then installed this update, you know, if they installed the module at this point, at this point it would work. If they installed it at this point, it will throw an error because you're removing this field. And the database schema, when they installed your module, it did not have that field. So, again, this is a very simple scenario. You could probably add a lot of ifs around this, etc. But it just goes to show you that calling Drupal get schema is not a good idea. And that even if it seems repetitive, the right way to do it would be to, again, repeat the schema that you actually want to create. Like, even if it's codeplication and your IDE is screaming at you that you're repeating 20 lines, 30 lines, 50 lines, just do it because that's the safe way. And like I mentioned, you know, exactly what schema your module is at every update hook definition. So, there is that. And I've definitely done this several times in client work. But you can't afford to do this when you're maintaining open source projects. Because, again, everything is unknown. Another subject that you have to be really mindful of, and this I hadn't really realized on service that are like maintaining open source, and especially Google, they definitely have a very strict policy and backwards compatibility. They have a promise, you know, terms of service, etc. You got to be mindful of deprecations and actually annotating your code. You might think you're writing your module, you know, it's not a big deal, but essentially every time you write a module, you're creating an API. And I've definitely looked at other modules and I've tried to extend them and I've called functions from the other stuff, you know, their services, etc. And if they change something, then, you know, you're screwed. So, what you got to do is be mindful that other developers might be extending your functionality, even if you weren't, didn't mean to. And if you really think something, you know, I have no idea if I'm going to refactor this, if this is still going to work, etc. Then go ahead and mark it as internal. It appears to be off and just mark it as internal. If it's not that people can make the assumption that they can extend that code, call that service, you know, call that public method, etc. And you might break stuff unintentionally and that can happen. Also, please specify composer dependencies. And I know we've all run into a situation where you download something and you install it and it's not working. And then you go read the docs and somewhere along the project page, not even on the readme, it says, oh, you need to download this project from somewhere else, etc. So, if possible, you know, I know there's some JavaScript libraries and such, but even then there's some trickery to make those work with Composer. You do have to specify every composer dependency and that will play nice with the ecosystem. I think at this point, everyone is familiar with Composer. It's just the way we're going to do it in Drupal. So, on that same lane, you know, you got to specify what versions of PHP your module is compatible with. Because there's a lot of PHP 7 yet are out there, you can make assumptions. And there's going to be a lot of new features in Drupal 8 that are very interesting to use. But if you're going to use them, then you're essentially making your module incompatible with PHP 7. So, you got to decide. And if it's not, you know, just pick and choose, but just let developers know what to expect. Now we touch on a very interesting subject of security. And I think this is interesting because when you're working on client side, you know, the business logic, and you just play around with it. You know, you got to be mindful of all these things, but you are, you know what you're trying to accomplish. So, there's an end result and you just do whatever you have to do to get there. But you need to have into account this different vulnerability types when you're writing code, even if it's a client side. So, first one, cross-site scripting, you know, don't allow essentially people to input JavaScript into text fields and stuff, so they can't execute them when it gets rendered. Access bypass, that would be having your code or your module allowing someone else to do stuff that they shouldn't. So, allowing access to another user, etc. Cross-site request forgery, allowing, you know, other websites to probably, like, submit information to your website and not validate that it's coming from the same domain. So, you know, they can have, like, those phishing pages from a bank, put your user password and then send it to your website and now they can change the password and things like that. Ascule injection, that's pretty obvious and it's definitely not good. Information disclosure is showing more information that what the permissions actually allow Drupal role to be displayed. Arbitrary PHP execution, please don't. Open redirect, that's having a way for your code to redirect to an external website. You know, perfectly constructed URL and that would have it redirect to an external website or something like that. Another Nile of service, that doesn't mean your site is experiencing other Nile of service, that means that some attacker can take control of your site so your site doesn't have the Nile of service to another website. So, you become a bot. I actually have a couple of experiences working with the Apigee team on security issues that were reported to the security Drupal team and it just goes to show, like, the mindset you have to have. So, for this one, there's a sub-module, the Apigee Edge team module that the whole point of working with Apigee and the developer portal is that you can create apps and those apps grant access to APIs and you get an API key and secret to interact with that API, right? Like any other developer portal. There's this functionality where you can have groups of users call teams, share the same app, so they share credentials. And what we had was, by the sign, we had all five was a good idea, to add a person to your team. Have this form where they start typing the email of the user you want to add, you ought to complete the email of the user and you add them to the team. And after a while, we realized that was an information disclosure of vulnerability. Because you're not certain where they're going to use this, you know, if that was intended functionality of whoever's going to be installing your module. You're actually disclosing the email addresses of any person, any user on your site by doing that. So, I was definitely leaking information that I should not because by the fall, you know, the fall configuration would be any user can be added to a team, any user can create teams when you install the module, that's the fall configuration. So, any user could see the email of any other user on the system. And that's not something that, you know, you can configure Drupal if you want to do that or not. So, what we ended up doing, you know, you go through this whole process with the security team where you can't make a release until they review the patch and you have to make a security announcement. All those pesky emails will get on Wednesdays and stuff like that. So, you push that security release. What we did for this case was have this process where you have an invite form and you type in the email, no auto complete. It will actually send an email to the user and let them know like an invites tab on their profile if they want to accept invitation or not. So, there's no disclosing of emails and that solves it. But it was interesting again because we all thought it was not a bug. We all designed it that way. And it was an information disclosure vulnerability because you got to take this whole mindset again into consideration. You know, is my site doing this? Will the users actually want that? You know, if that's the functionality they want, they can definitely adjust it or extend the code or something else. But you can't make those assumptions anymore. This was another security issue. The API keys are cached on the client side. So, again, you get your page where you can see your app with the API key and secret. But those are like little dashes or something. And you click show and it will do an Ajax request and it will fetch that and actually display the API key and secret. Well, that Ajax request was sending in the header of cash control must revalidate, no cash, private, you know, super hardy. It turns out no cash does not mean that it won't get cash. It just means that it needs to be revalidated when it's going to get reused by the browser. So, someone with a program in their Mac, their Windows machine or something can actually read the sessions from the browser and extract that information and they would be able to extract API keys from any user that was visiting the site. And the run header to use was cash control, no store. So, again, it's like you really got to look into security and yeah, it was something that we thought we just weren't aware that worked that way. Just some questions to ask when you're building something. This is a security risk calculator from Drupal. That's how they assess like how risky and the security issue is. But it does have some questions that you can ask yourself. You know, while you're building your functionality, etc., like if they do this, you know, will the user installing this module have access to information they would not be having access to? Will this cost non-public data to be accessible? Is there a way that, you know, it can write to the database some way that you would not like to? So, yeah, those are just like considerations. That's the risk calculator from Drupal. That's what they use. And again, it comes down, my conclusion was like, don't assume business logic. Don't assume that what you would think is a good idea. It's a good idea for everyone. You got to run out through those tests and actually, you know, make sure it matches all those criterias. Handled private data super carefully. Should be an assumption everyone makes, but it's difficult when you're working on projects. Again, it's just this mindset of working like on client websites where they request like, oh, I want to complete my face shirts and blah, blah, blah, blah, blah. But, you know, you can't have those assumptions when you're working on open source. Definitely sanitize all inputs. And a lot of these things you get by just using Drupal APIs correctly. You use a form API, you know, it has that token hidden field in there that protects against cross-site request forgeries. You use the database API, you know, that will sanitize so there's no SQL injections and things like that. Drupal does a lot of that for you, but again, they can't implement logic that you got to think about all those other questions beforehand. So there's also a writing secure code guide on Drupal. I'm linking to that if someone wants to check the slides. I'll post it on my Twitter later on. Some more considerations, but these were the ones that really got me while working. Next topic would be testing. And like my toddler says, why? Why not? Because every time you have a regression, like a bug, you should be writing tests so you don't have regression, right? You're not working on a project site where someone is just going to, you know, an editor will go in and QA and make sure everything looks fine. It's so easy to miss something when you're developing something because you're writing that functionality, you know how it works, you know everything. So you're essentially biased against your own module. You got to have something that test your user stories the way they were written. So you definitely got to have testing in there if the budget allows for it. What to test? Again, this comes down to experience. You might test individual classes if they have some complex logic. And you might go as far as, you know, using PHP unit, using mods, and that's where you're supposed to write to interfaces and not classes, then that you can log your classes, etc. A bunch of more technical stuff, but there's, you know, testing individual classes, integration tests where you test a whole functionality of your module, making sure, you know, the whole interaction, any service you have, it will get run correctly for everything. And Drupal has great tools to test. They use PHP unit, and you can run your tests either on pre-commit hooks, at least some code linting or something, and you can run it through CI. Drupal has all of that, you know, you can run externally. We used to have the projects on GitHub and use CircleCI, now we're using GitHub Action, so there's a lot of support for that. And very briefly, there are four types of unit testing in Drupal, and it's good to know what those are. Unit testing is just like single class testing. Minimal dependencies is just, they're super, super quick, like milliseconds quick. Kernel tests. They're tests with a bootstrap Drupal kernel, so when you're running any of these PHP unit tests, what Drupal does to run the test is it will create a new site with an empty database, and it will run the tests in there. So when you write kernel tests, it's just, you know, it's a new site. It will run a very minimized kernel. You manually have to specify in your test setup function what modules you want to enable from core, what database schemas you need enabled for that. So it is, it definitely takes a learning curve to write PHP unit tests, and I would say half of the time I was working with Apigee was like figuring out why tests, you know, didn't execute, they were complaining about a dependency missing or something like that. But that's what made the project stable. Functional tests, they actually run on a fully bootstrap Drupal instance. So you don't get that quickness of like what modules should I install, should I install this module schema, this module configuration, etc. You get like a Drupal installation working. Obviously, they'll take way longer to execute because you have to install Drupal. And, you know, even if you're doing through my SQL database, you know, any other type of database, it still takes pretty long to run a functional test. And functional JavaScript tests are the same, but they use a web driver so that you can QA Ajax functionality essentially. You can need to check the UI for that. So those are the main tests that you can use PHP unit for that. And how we're doing on time. Okay. This is an example, like how far you should go for testing. It really depends on your project. Like for this project, there was a requirement to have it compatible with multiple PHP versions and have it compatible with multiple Drupal versions as well. So we had that test matrix of PHP seven, four, eight and eight dot one, and several Drupal versions. And additionally, there's two types of back ends with Apigee. There's Apigee Edge and Apigee X. So each of those ran against each other, you know, so a whole test week took about 40 minutes or so. So total runtime was, I don't know, like 10 hours or something. Again, they would run in parallel. So it was about an hour, but that was not it. It was it was a lot. Most projects will not require likely that much. And but yeah, you can see we're doing the basic setup and like it's running PHP units and then by PHP kernel units, and then it's running functional JavaScript test, etc. There's a lot of tests to show if someone's interested around that. But yeah, that's an example of how you can run your test. And then it comes the fun part of actually doing a release, bundling a release. It's not a deployment. A release is not a deployment. It's actually a release. It's a different thing and it's a different mindset. So when you're doing a deployment again, you know what you're doing and you're pushing the code and you know everything that has to be known. When you're doing a release, someone else is going to be taking that code and trying to figure out what to do with the code you're pushing. So number one, please specify what type of release it is. You know, sometimes developers need to know if they actually need to install that. So there's a security update. Do I need to do it tomorrow? Can I do it in a month? Is it a bug fix? Is it going to fix what I have trouble with? Or it's adding new features? Maybe that can't wait, right? Right. Relevant release notes. We've all been there. Like new features and bug fixes. A link to a ticket number does not really help either. You know, sure we could go read, you know, everyone knows Drupal issues are like super long and we don't really know the conclusions. So just summary, you know, make a brief summary of what it's doing. Probably link to the ticket, but make it easy for people to read the documentation for your module. And lastly, use symmetric versioning. This was not really something in Drupal until lately, but it allows it now. And if you're using, you know, you're marking things as deprecated and all of that, you should follow this notation. You know, the major version is when you're making, breaking API changes, you know, Drupal eight to nine, they remove all the deprecated codes. That's not going to work. So you release a major version, minor version, you add a functionality patch version. You're just using bug fixes. So I very rarely see Drupal use like patch versions. I guess it's just out of the way of doing things in Drupal, but we could use it. So let's use it. Okay, everyone knows this, right? Something doesn't work. So once you make a release, the only way to fix a problem is to make a new release. That's really painful because, you know, you make mistakes and sometimes they're pretty simple, darn mistakes. Okay, dumb mistake. I tacked the wrong, the wrong commit. I was, this release was the same as 2.0. I got the wrong tack. So there you go. And when just you push that, you make a release, there's no the leading other like release on Drupal. It's there for history for everyone to read. It just happens. It's a fun meeting after that. I love the group module. It's one of my favorite modules. It's got a great API, but you can see they've been through a lot. Or piano, Friday night, what can go wrong? Can't believe it's finally here. What am I going to do with my life? I should have known known grants were a powerful enemy. When it rains of pores released, maybe this one won't melt release. And I just want to call attention to this great developer. Like they properly tacked everything. The release notes are clear. It tells you what they're doing in a like very two line summary. Whether they're bug fixes, this version isn't secure, don't use it. You know, we remove some note access grants, you know, etc. So it's very clear what they're trying to accomplish in each release. And just gives you sympathy for the poor guy maintainers. And then the last note would be, you know, again, you're maintaining something that's meant to be consumed by others. So try to write the documentation and support. Release notes was one example of that. If you have a module, you can write some documentation, you know, Drupal, GitHub, wherever you're maintaining your stuff. Have good readme, good guides. There's a lot to be said on that. I don't know how many times we've all used modules and you download something and you have no idea how to use it. You end up going like to the routing definition file to see the configuration page and even things like that. So, you know, just try to write some documentation even if it's free. Look at the issue queue. There might be some interesting stuff reported there that you might or might not want to implement. And just groom your backlog and if it's something that you're not interested in developing, just state it or, you know, just try to close tickets, long lost tickets from Six years back and so on. And as always, polite. You will be amazed. And if you are looking for some support, then try to make it easy for people, you know, write some getting started docs, how to contribute to your project, have some Docker images that they can just download and get started, specify what coding style you're looking at, you know, so you don't have to comment on PRs, for example, things like, you know, brackets or things like that. Especially if you're running tests, you know, how to run tests and what CI process you're expected to go through. And on the final note, maybe some people don't care about open source and then you only maintain client sites. And again, that's most of our day to day operations. And just looking back on the experience, I figured out like most of this still applies. You should always have that mindset. And by applying this mindset, it has to make me a better developer because I don't try to second guess myself anymore. I don't make assumptions. You know, like in that deployment scenario that I mentioned of creating like a custom block, et cetera, I like, I don't need to write instructions for who's doing the deployment if I get sick. I just write, you write proper update books and people can do it, et cetera. So it just makes it everything clear. And everything is structured and well declared. So it just makes you a better developer and you're taking into consideration client security, all of those performance improvements. And I think the only tough part would be writing tests for client sites. And what I would like to share with that, I have a link, it's not part of these slides. But there's this great test suite. I don't know if you guys seen it, Drupal test traits. It's essentially PHP unit. But instead of running PHP unit test on an empty database, you can actually run it on a client side. So you can write your Drupal code like I'm very comfortable. I'm a backend developer. I'm comfortable writing like module code. I can write my test using all of that functionality. Because I've never been comfortable like in Behad or things like that, or they're just like too much overhead. So you can write your test using PHP unit and run it on a client side. Obviously you would not like to run it on a production side. You would like to integrate it into either testing on your local environment or have CI create a clone of a dev environment or something and then run the test there. But this has been a life changer. And I work in a lot of access control, customizing access control, I would say, and doing some heavy modifications. Like I mentioned, with the group module and other things like that. And you know, sites start very simple requirements. And then, oh, can you add this? And we also need this. And then we need to add this. And just by having PHP unit tests that actually test on a client side with their configuration, I've been very much confident on deployments that we don't break stuff. And we don't have regression books. So there's still a lot that you can learn from these. This is super useful. And again, like I mentioned, I posted some links in here of how you can write more secure code. This is great documentation about vulnerabilities. It even lets you play around with some labs. So you can like simulate those vulnerabilities, et cetera. Documentation, so you get to read that no cache can actually back be cached. PHP unit, et cetera. And this is an example, like it's a very complex, how to contribute, but it has everything. This is from the Apigee project. You know, we actually got to accept the contributor license agreement. All contribution goes to code review, community guidelines. But you might want to do it like from here, how to get started, work this project. If you need to do anything, there are some simpler guides out there. But this is very comprehensive. You know, if you have some complex requirements for your module, it's good to get people to know all of that and just be able to contribute. There's always so much more than you can do. Those are just random tidbits that I have. Happy to answer any questions or comments from people. So, on the Drupal treats that you were mentioning before, is that only for unit testing? You were mentioning VHP, so it's also for data-driven tests? Because you want to do that? Or also visual regression testing? You can do some visual regression testing. It's not VHP. Again, you're still writing like PHP unit tests. But you can run visual regression tests at up to a certain level, but not like the comparison that you would get with another tool like BackstopJS or something. You would actually use PHP unit to look into the HTML structure of the page you fetched and see if it has class, if it has, you know, look into the HTML itself. You check if the link express and it's not pressing, things like that. So, it's not properly like... Yeah. So, when you're talking about continuous integration, in this particular project or any project, when do you recommend to use actually data-driven tests or just stay with PHP unit? I think data-driven tests make sense for a client website. Okay. Yeah. So, that one will test on your client live site, right? Is that what I understand? Yeah. Because at that point, you want to make sure that the site looks good with all the configuration that's currently on the site, not just the single module configuration. You want to make sure, you know, that the data that the client has that looks the same, that you're not making any breaking changes on the UI and things like that. So, I would suggest that. And when we talk about individualized and the functionalized like a module, PHP unit should be enough, right? Like... Yeah. Yeah. So, based on the timeline that you said, it sounded like you probably took over the Apigee project for the Drupal 8 port? The Drupal 7 version existed? Yeah. The Drupal 7 version existed. We didn't help... We were randomly just helping out people, but there was another company that helped to build the Apigee Edge initial port to Drupal 8. And then we took over a maintainership of that module and with the help of chapter 3, it wasn't just me. We did Apigee monetization modules for Apigee API catalog and the kickstart distribution. The question still applies. Yeah. I was wondering, like, when you took it over, how much of their sort of existing infrastructure did you use in terms of like policies and... From the Drupal 7 stuff? From whatever you took it over from. I mean, the Drupal 8 was a complete rebuild. So, there was no refactoring. You know, it actually got like a clean slate of user stories for the Drupal 8 stuff. There was definitely an intent to copy some functionality from the integrated portals, you know, functionality that comes directly from Apigee. But the idea was not to have an exact clone, but to have it flexible. And there were some features that were only going to be supported with Drupal as well. Okay. So, what you ended up doing had more to do with the integrated portal than with the previous version of the Drupal portal? No, we didn't touch integrated portal. We just built something that would initially provide the functionality that came with integrated portals, you know, so users could have like similar functionality, but then they would be able to use Drupal to do anything they wanted to. Yeah. Really, some features coming from the integrated portal and most of the features from the Drupal 7 portal was the product of that port. With the addition of a new way to using distributions to set up how to set up a whole developed portal, it would be an easy route. Because the starter kit wasn't a D71? No, it wasn't. And it's also, if you want to use a developed portal on D71, basically, you know, they would set it up.