Drupal Planet

Subscribe to Drupal Planet feed
Drupal.org - aggregated feeds in category Planet Drupal
Updated: 1 day 14 hours ago

FFW Agency: The ABC's of Drupal: Basic Page, Core and Multi-Site

June 26, 2017 - 1:27pm
The ABC's of Drupal: Basic Page, Core and Multi-Site Ray Saltini Mon, 06/26/2017 - 16:27

For anyone who's ever looked up a definition of a Drupal term and been left wondering what it all means, here are some practical real world explanations you can use to navigate the Drupalverse. Watch this space and use comments to send us your feedback and requests.

Basic page

One of two default content types that ships preconfigured in Drupal core since Drupal 7. As a content type, Basic Page is made up of Title and Body fields.

All content types are part of Drupal’s core fields system and can be customized with different fields. The output of a Basic Page is configured to appear either as a teaser or a full version, sometimes referred to as ‘default' or as a node.  Unlike the Article content type, content created with the Basic Page content type is not by default configured to appear on the home page. Basic pages are often used on simple websites or for static displays of content such as an About Us page that is linked to from the main menu.

It’s helpful to understand that though its content type name is basic page, it is in point of fact it is neither basic nor a page. As a content type it uses fields and can be extensively customized and is no less suited to advanced configuration than any other content type. Nor is it as configured by default in Drupal a page strictly speaking. Content type forms are used to create content nodes, or entities, that are typically used as the main element in an assembly of other items, such as menus, images, lists, and blurbs that are arranged in blocks around the main content.

Core

Drupal’s basic system code and feature set is bundled together in different major versions referred to as Drupal Core that can be downloaded from the Drupal.org website and installed through various methods.

Not all features are active in Drupal core and must be turned on if desired. The key concept behind core is that to take full advantage of code updates no one should ever, ever ‘hack’ core for use in a production web site. While there are some exceptions, a hacked core is usually a sign of inexperience working with Drupal or negligence.

Major versions of Drupal core have been identified as whole numbers since Drupal 5. Released on November 19, 2015, Drupal 8’s core was completely refactored around the Symfony2 PHP framework, representing a major shift in how Drupal websites and applications would be architected and constructed as well as how the Drupal platform could be used. Drupal 7 is still fully supported and used to power thousands of web properties. 

Multisite

The term ‘multi-site’ is sometimes thrown around casually as a reference to more than one Drupal powered website.

The strict definition of multi-site refers to an installation of Drupal that runs more than one website off a single code base. However there are many different ways to configure a multi-site installation. Sites can share and run off the exact same code, or be extended or customized with other code per site. It’s possible to have several sites run off exactly the same code while other sites each run off a combination of shared code and code that is specific for each site.

Consider any multi-site effort very carefully. The decision to go multi-site should include considerations around hosting, IT capacity, and development workflows. 

Tagged with Comments
Categories: Blogs

Elevated Third: What is an Acquia Certified Grand Master

June 26, 2017 - 1:18pm
What is an Acquia Certified Grand Master What is an Acquia Certified Grand Master Zach Ettelman Mon, 06/26/2017 - 10:18

Big congratulations to Tanner Langley, our Senior Drupal Developer turned Acquia Certified Grand Master. He has earned the highest ranking Drupal certification.

Elevated Third is lucky to now have 3 of the world’s 150 Acquia Certified Grand Masters. Our Grand Master Drupal developers, Nick Switzer, Michael Lander, and Tanner Langley are incredibly important to our implementation workflow. They work on every website we launch.

In order to help everyone understand the significance of this certification, we’ll discuss what a Grand Master is, what the certification process looks like, and the value a Grand Master provides.

  What is an Acquia Certified Grand Master?

Let’s take a step back to understand Acquia’s role in this certification process. Acquia is the leading cloud platform SaaS company for Drupal websites and is led by Dries Buytaert, the creator of Drupal. They serve as the administrator and regulator of the premier professional certification program for Drupal. Having Dries Buytaert, the co-founder of Acquia and the creator of Drupal, develop this program provides an enormous amount of credibility. It demonstrates Acquia’s commitment to not only their platform but also to the whole Drupal community. With more knowledgeable and engaged developers bettering the community, Drupal’s power increases and in turn supports the companies that leverage Drupal for their digital experiences.

To become an Acquia Certified Grand Master a developer has a one-year timeframe to complete three certification exams: Certified Developer, Certified Front End Specialist, and Certified Back End Specialist. Developers interested in taking the exams and becoming Grand Master certified can take the test online at any time, at testing centers across the world, or during DrupalCon, where most developers take the tests. 

  Why Does It Matter?

Passing three of the hardest Drupal certification exams is a daunting task and those who pass the test should be applauded. But how does the time and effort into passing these certification exams translate to project success?

When working with a Grand Master Drupal developer you are getting one of the top developers in the world and the knowledge of Drupal best practices and efficiency in mind. Staying up-to-date on industry trends and best practices gives them a unique perspective on the next Drupal challenge.

Most importantly, our clients gain an incredible competitive advantage having access to Grand Master developers on their projects. Top Drupal talent in the industry gives our clients access to developers that are always looking for new ways to innovate and develop modules that deliver never before seen functionality within Drupal.

Our Grand Master certified developers recently used their knowledge to develop a fully decoupled Drupal project, that has been highly successful for our client and serves as a benchmark in the industry.

Let's take a deeper look into what is required of an Acquia Certified Grand Master. 

  Acquia Certified Developer Exam

The more general of the three exams focus on the areas of fundamental web concepts, site building, front end development (theming), and back end development (coding). Specifically, the exam tests a developer’s level of knowledge and ability to:

  • Setup and configure new Drupal sites
  • Develop and implement new Drupal modules and themes
  • Customize and extend existing modules
  Acquia Certified Front End Specialist Exam

This test specifically focuses on a developer’s skills and knowledge of Drupal front end theming, including:

  • Fundamental web development concepts, HTML, CSS, Javascript, PHP, jQuery
  • Theming concepts like custom regions, theme configuration, stylesheets, breakpoints, sub-themes
  • Templates and pre-process functions, Twig syntax, templating, Form Alter and Template Suggestions After
  • Layout configuration, Blocks, views, and the Responsive Image module
  • Performance/security, analyzing and resolving site performance/security issues from site configuration and custom themes

 

Acquia Certified Back End Specialist Exam

This exam validates the skills and knowledge of building and implementing Drupal solutions through module development. This test focuses on:

  • Fundamental web development concepts, HTML, CSS, Javascript, PHP programming, managing dependencies using Composer, Git for version control, and Automated Testing concepts
  • Drupal core API. Registering paths for URL requests using Routing system and Menu API, building and validating forms using Form API, interact with Entity system using Entity API, and ability to use Core APIs for building and extending Drupal functionality
  • Debug code and troubleshooting
  • Theme integration
  • Performance
  • Security
  • Leveraging community by contributing modules back to the Drupal community and ability to write code using Drupal Coding Standards

To know there is a Grand Master on your project is to know your project will be influenced by someone with a well-rounded background of the entire development Drupal landscape, not just specialization in front end or back end development. This well-rounded background helps the developer have a better understanding of how the different pieces of a project come together to create a powerful, flexible, and scalable digital platform.

Quite simply, having Grand Masters on our team ensures our clients get the top talent in the industry to develop their sites and produce extraordinary digital experiences for their own customers. 

If you are looking for top Drupal talent for your project, let's talk

Categories: Blogs

Valuebound: How to manipulate pricing using Order Processor in Commerce 2.x

June 26, 2017 - 11:36am

Highly customizable business logic is the core of any e-commerce solution. For such functionality, Order processor comes in picture which helps us to define flexible pricing with dynamic discounts.

In this article I will walk you through order price manipulation and use of custom adjustment to fulfill complex business logics in Drupal commerce 2.x module.

What is Order Processor?

E-commerce order processing is part of the order refresh process. This is executed on draft orders to ensure that it has up to date adjustments and that its order items are up to date. E-commerce order processing is a part of e-commerce workflow in drupal commerce 2.x.

What are Adjustments in Drupal…

Categories: Blogs

Droptica: Droptica: Codeception - how to start automatic tests using docker-console

June 26, 2017 - 7:51am
If you read our previous posts, you already know very well how to start a project in the docker-console. If you haven’t done it yet, you should start with this article, because for the purpose of this article we assume that your project in the docker-console is already up and running, therefore all commands executed below will refer to it. In this article, we would like to introduce you to the world of automatic tests using Codeception, based on this kind of a project. Of course, not everyone has to automate all the tests on their projects, but if this will not require too much work, we bet that many people will look at a set of “smoke tests” favourably, to say the least. 
Categories: Blogs

heykarthikwithu: Drupal 8 - Introduction to Plugin System and how its awesome in Drupal 8?

June 26, 2017 - 4:22am
Drupal 8 - Introduction to Plugin System and how its awesome in Drupal 8?

Drupal 8, Introduction to Plugin System, and how plugin system is awesome in drupal 8?

heykarthikwithu Monday, 26 June 2017 - 12:52:01 - IST, Asia/Kolkata
Categories: Blogs

Dale McGladdery: Site Reset BASH Script

June 23, 2017 - 5:57pm

I'm experimenting with Drupal 8 and some its new features like migration and configuration management. A reset script is a convenient timesaver and many people have shared their techniques for doing so. Having benefited from other's generosity I wanted to return the favour by sharing my current work-in-progress.

This script:

  • Leaves the codebase as-is
  • Deletes and reinstalls the database
  • Deletes the 'files' directory
  • Deletes the specified configuration management directory
  • Enables additional modules, as required
  • Deletes settings.php and allows Drupal install to recreate
  • Updates the new settings.php $config_directories['sync'] entry
  • Adds a settings-migrate.php include to settings.php

I call the script via Drush with an entry in drushrc.php:

$options['shell-aliases']['428-reset'] = '!sh /Users/dale/.drush/sha-scripts/g428-reset.sh';

The script is evolving as I learn more about Drupal 8 and refine my workflow. Comments and suggestions are welcome.

drupal-reset.sh:

#!/bin/bash

# Reinstall a Drupal instance to reset it back to a know state.
# A file base and Drush alias must already be configured.

DRUSH8='/Users/dale/bin/drush8/vendor/bin/drush'
DRUPALDIR='/Users/dale/Sites/group428'
CONFIGDIR='sites/default/group42config/sync'
DRUSHID='@g428'
SITE_NAME='Group 428'
ACCOUNT='admin'
PASS='staring-password'
EMAIL='no-reply@group42.ca'
DB_URL='mysql://group428:group428@localhost/group428'


# Nuke the database
$DRUSH8 $DRUSHID sql-drop --yes

# Nuke the filebase
echo "Resetting files"
chmod -R u+w $DRUPALDIR/*
rm $DRUPALDIR/sites/default/settings.php
rm -r $DRUPALDIR/sites/default/files
rm -r $DRUPALDIR/$CONFIGDIR

# Fresh Drupal install
cd $DRUPALDIR
$DRUSH8 site-install standard --db-url=$DB_URL --site-name=$SITE_NAME --account-name=$ACCOUNT --account-pass=$PASS --account-mail=$EMAIL --yes

# Base configuration
$DRUSH8 $DRUSHID en admin_toolbar,admin_toolbar_tools --yes

# Allow upcoming changes to settings.php
chmod u+w $DRUPALDIR/sites/default
chmod u+w $DRUPALDIR/sites/default/settings.php

# Configuration Management
sed -i '' "/config\_directories\['sync'\]/d" $DRUPALDIR/sites/default/settings.php
echo "\$config_directories['sync'] = '$CONFIGDIR';" >> $DRUPALDIR/sites/default/settings.php

# Migrate
echo "\ninclude 'settings-migrate.php';" >> $DRUPALDIR/sites/default/settings.php
$DRUSH8 $DRUSHID en migrate,migrate_drupal,migrate_plus,migrate_tools,migrate_upgrade --yes

# Login
$DRUSH8 $DRUSHID uli Tagged: AttachmentSize Drupal Reset BASH Script1.43 KB
Categories: Blogs

LevelTen Interactive: Higher Education Websites on Drupal

June 23, 2017 - 12:03am

This summer, LevelTen started the Web & Drupal Developer Internship program and we've brought on 3 up and coming developer interns! In today's post, we introduce you to Jerad Steward, and his experience and research with working in Higher Education Drupal websites:

Recently I had the opportunity to be paid to look at a number of sites, even though I do that for free on my own time. I obviously jumped on that offer and read through some case studies on why Universities and other higher education seem to gravitate towards Drupal as a solution to their website needs.

I...Read more

Categories: Blogs

LevelTen Interactive: Travel Websites Built with Drupal

June 22, 2017 - 5:46pm

This summer, LevelTen brought back the Web & Drupal Developer Internship program and we've brought on 3 up and coming developer interns! In today's post, Anima Bajracharya, and her research assignment with of Drupal Travel websites:

After researching some of the case studies of Travel sites created in Drupal, I found out that Drupal can help businesses across any industry to create rich digital experiences. It is no surprise that more than one million sites trust Drupal today. With benefits such as scalability, free modules, responsive design, flexible APIs and one of the...Read more

Categories: Blogs

Drupal Association blog: Announcement: Board Meeting and Executive Session - June 28, 2017

June 22, 2017 - 9:53am

On June 28, 2017 at 12:00 PDT/20:00 BST, The Drupal Association will host a one-hour virtual board meeting for the public to attend. It will be followed by an executive session, which is a private session for the board members.  We invite the public to join our board meeting via zoom or you can dial in with the following information:

Board Meeting Agenda

The Board Meeting Agenda includes:

  • An Executive Update covering the following topics and speakers

    • Community Discussions update from Whitney Hess

    • DrupalCon Baltimore Wrap

    • DrupalCon RFP update

    • Marketing Initiative / Review of Drupal.org privacy policy

    • Drupal.org Infrastructure RFP Update

  • Financial Update from Summit CPA

  • Q&A with the Drupal Association board

  • Q&A with the community attendees

  • The Board votes to approve Jan - April 2017 financial statements

After the meeting, we will post a blog that shares more details about the meeting and we wil post the board materials and meeting minutes here.

Executive Session Agenda

While the The Executive Session is a private meeting amongst board members, we want to provide insight into what the agenda topics will be.

  • The Finance Committee will provide an overview of the 2016 financial audit and answer questions.

  • Discuss Drupal Association Board Executive Committee composition for 2017-2018 term.

  • The Governance Committee will provide an update and recommendation on how the Drupal Association can continue to support the community as they determine how to evolve community governance.

  • The Nominating Committee will provide an update on the progress with identifying new board member candidates for the three seats that expire in November 2017. Learn more about the Drupal Association board here.

We hope you can join us to learn more about Drupal Association operations and to have your questions answered by the Drupal Association Board and staff.

Categories: Blogs

Amazee Labs: Lead Developer UK Conference 2017, Day 2

June 22, 2017 - 9:34am
Lead Developer UK Conference 2017, Day 2

This is part 2 of my summary from the Lead Developer UK conference. If you want to refresh your memory about what happened on Day 1 you can skip back for part 1, or alternatively continue reading about my highlights from the second day of this outstanding conference.

admin Thu, 06/22/2017 - 14:34

Kevin GoldsmithFail Fast, Fail Smart, Succeed started day two with the recommendation that we shouldn’t punish failure but we should make sure that we learn from our mistakes. Nothing can be more harmful than a culture that prevents talking about failure. Instead, when we learn to talk about our mistakes, others and ourselves will be able to get better much faster. I liked Kevin’s recommendation about creating a shared repository for the team to collect learnings they have made along the way.

Fail Safe, Fail Smart, Succeed from Kevin Goldsmith

Mathias MeyerBuilding and Scaling a Distributed and Inclusive Team gave some valuable insights into his experience at Travis CI. Having the team distributed across continents creates challenges such as when cultural mentalities differ, i.e. some would expect more direct communication while others are used to talk less directly about issues (remember ask vs. guess cultures from part 1?).

I liked the idea of setting up a lot of decision making processes asynchronously via github pull requests, so that team members can contribute at their individual pace. Also, Travis is using special incident response channels for teams on Slack where they collaborate on important tasks in a timely manner.

Randall KoutnikImplementers, Solvers, and Finders: Rethinking the Developer Career Path encouraged the audience to think beyond the classical categories of Junior, Regular and Senior developers. At a first stage, an implementer would give a solution specification and make it happen.

To level up, developers would become solvers that come up with their own solutions to given problems and in the latest stage, they would find their own problems. Think about providing context like a problem space or a given product and you delegate more responsibility to that person so she will need to find possible problems herself. 

Carly RobinsonMentoring Junior Engineers @ Slack HQ shared her personal career path and how she was mentored as a junior. Small startups often struggle with the task of providing the necessary mentorship for their juniors, so it was great to see such a success story. Carly mentioned that for her mentorship is a relationship and you need to establish a good foundation upfront between the mentor and the mentee. Setting goals, tracking progress and acknowledging success are important tools for successful mentorship.

Similarly, being aware of your own emotions is important when reviewing another person’s work. Your initial reaction might be “This is dumb, I know how to fix this.” Instead, by being able to step back when having that reaction and reframing it into something like “Why did that person do that thing?” may allow you to reflect and discover the underlying issues and help come to a solution more collaboratively. 

Overall, I got back from the Lead Developer conference with a lot of inspiration. It’s great to see that so many successful leaders talk about the same topics and mention that it’s worthwhile focusing on problems I face and try to tackle them everyday. For me, growing leadership skills is a continuous effort that takes a lot of self reflection and discipline. It might be easy to agree that points like “giving positive feedback” is the right thing to do, but implementing it into one self’s daily practice takes effort and practice.

Slides of all talks mentioned above and more can be found on the conference website. I’d like to thank the whole organizing team for setting-up an incredible line-up and making sure the code of conduct doesn’t feel like something added as a afterthought, but ensuring diversity & inclusion was something that was really to the core of the Lead Developer conference. Next year’s events will happen in Austin, New York and London.

Categories: Blogs

Drupal Association blog: Drupal Association Q4 2016 Financial Update

June 22, 2017 - 9:24am

As part of the Drupal Association Board's duty, board members met in April and approved the Q4 2016 financial statements. Now, we are able to share them with the community to provide transparency and clarity. You can find the financial statements here, which include the Income Statement and Balance Sheet for each month. Our cash balances are located on the balance sheet, located in the Asset section (first part of the balance sheet) called "cash and cash equivalents.

In this blog post, we will answer the following questions:

  1. How did we perform financially this quarter?

  2. How did we perform financially through the end of 2016?

  3. How can we perform better in 2017?

Setting Performance Metrics

To answer #1 and #2, we need to know what success looks like. As they say, if you can’t measure it, you can’t manage it. The Drupal Association works with a virtual CFO firm called Summit CPA, who creates our monthly financial reports as well as sets our financial KPIs, making sure we are working towards goals that ensure the Drupal Association’s sustainability.

Since the Drupal Association’s cash reserves were depleted due to investments in Drupal.org improvements especially to support Drupal 8’s release, Summit recommends that we rebuild our cash reserves and achieve a cash reserve KPI of 15%-30% of estimated twelve-month revenue. Since Drupal’s revenue and expenditures drastically fluctuate from month to month due to DrupalCon’s large cash outlay, a cash reserve goal closer to 30% is the ideal goal.

To rebuild our cash reserves, we need to create an operating profit to fill the reserve. To do this, Summit recommends that our second KPI is to achieve a Net Income Margin of 10%.

Q4 2016 Performance

Since Q4 2016 is near the beginning of our financial turnaround, we will see improvements with both KPIs over time. It is also important to note that Q4 is historically when our cash is lowest. It is the period of time that is between DrupalCon Europe, which operated at a loss, and DrupalCon North America, which rebuilds our cash since it is a profitable event.

Below is our KPI progress in Q4 2016.

KPI

GOAL

OCTOBER

NOVEMBER

DECEMBER

CASH RESERVE

15-30%

11%

9%

8%

NET INCOME MARGIN %

10%

1%

1%

0%

2016 End of Year Performance

2016 was a challenging year financially as we drastically reduced costs by laying off 40% of our staff and eliminating our Portland office. While these corrections were difficult, they set the organization on a sustainable path.

While we continued to remain cash positive by the end of 2016 (see Cash Flow chart below), we operated at a loss, which was anticipated. In positive news, we reduced the losses by about $145,000 (see Forecast vs Actual table below).

Chart: Cash Flow

(*This chart shows the Drupal Association’s cash flow. It uses actual data from January 2015 to December 2016 and uses forecasted data from January 2017 to April 2017. )

Table: 2016 Actual vs Forecast

Areas of focus in 2017

With these 2016 improvements in place, 2017 is positioned to be a healthier year financially for the Drupal Association. To ensure a stronger year, we conducted a margin analysis of our programs to see where we need to focus.

From this study, we found several areas to focus in 2017 that create value for the community while also improving our financial health. Areas of focus include:

  • Make DrupalCon Europe a financially sustainable event that continues to provide value

  • Grow DrupalCon North America attendance through improved marketing and attracting more end users with customer content such as industry summits and case studies.

  • Create more value for Supporting Partners to grow participation and create a program for End Users to join.

  • Improve the Drupal adoption journey off of the Drupal.org front page by including content from Drupal businesses that provide value for the visitors and branding or leads for the Drupal businesses who provide the content.

  • Identify ways to reduce costs associated with Drupal.org by studying the sites and services the Drupal Association provides to see if we can reduce associated costs.

We are hard at work making the above improvements and starting to see encouraging results. Starting with our 2017 quarterly updates, we will provide more clarity into our financial portfolio and how each program performed.

File attachments:  image1.png image2.png Drupal Association - Q4 2016 Financial Statements (1).pdf
Categories: Blogs

Agiledrop.com Blog: AGILEDROP: DrupalCon sessions about PHP

June 22, 2017 - 4:57am
Last time, we gathered together DrupalCon Baltimore sessions about DevOps. Before that, we explored the area of Front End, Site Building, Drupal Showcase, Coding and Development, Project Management and Case Studies. And that was not our last stop. This time, we looked at sessions that were presented in the area of PHP. Advanced debugging techniques from Patrick Allaert This session was not about Xdebug. It was about tools that let you know what’s really happening in your PHP code. Tools like the phpdbg debugger, process tracing tools like strace, ltrace, the Linux inotify mechanism,… READ MORE
Categories: Blogs

myDropWizard.com: Drupal 6 not affected by SA-CORE-2017-003!

June 21, 2017 - 7:50pm

Today, there were Critical security releases for Drupal 7 & 8:

https://www.drupal.org/SA-CORE-2017-003

We received a couple e-mails asking if it affected Drupal 6, so I decided to post this short article to say:

Happily, Drupal 6 is not affected! :-)

Of the 3 vulnerabilities in that SA, the two Drupal 8 ones don't apply to Drupal 6: it doesn't have REST or YAML support.

We did extensive testing to see if the Drupal 7 one applied to Drupal 6, including, testing the 'upload' module (in Drupal 6 core) and with the contrib 'filefield' and 'webform' modules and couldn't reproduce the vulnerability.

(FYI, since we have access to the private Drupal security queue, we did our testing several months ago :-))

So, if you still use Drupal 6, you don't need to worry about a core update today!


Categories: Blogs

myDropWizard.com: Drupal 6 security update for Search 404

June 21, 2017 - 5:35pm

As you may know, Drupal 6 has reached End-of-Life (EOL) which means the Drupal Security Team is no longer doing Security Advisories or working on security patches for Drupal 6 core or contrib modules - but the Drupal 6 LTS vendors are and we're one of them!

Today, there is a Moderately Critical security release for the Search 404 module to fix an Cross Site Scripting (XSS) vulnerability.

From the security advisory for Drupal 7:

The Search 404 module enables you to redirect 404 pages to a search page on the site for the keywords in the url that was not found.

The module did not filter administrator-provided text before displaying it to the user on the 404 page creating a Cross Site Scripting (XSS) vulnerability.

This vulnerability is mitigated by the fact that an attacker must have a role with the permission "administer search".

Here you can download the Drupal 6 patch.

If you have a Drupal 6 site using the Site Verify module, we recommend you update immediately.

If you'd like all your Drupal 6 modules to receive security updates and have the fixes deployed the same day they're released, please check out our D6LTS plans.

Note: if you use the myDropWizard module (totally free!), you'll be alerted to these and any future security updates, and will be able to use drush to install them (even though they won't necessarily have a release on Drupal.org).

Categories: Blogs

Lullabot: How to Embed Just About Anything in Drupal 8 WYSIWYG with Entity Embed and URL Embed

June 21, 2017 - 11:31am


Embedding media assets (images, videos, related content, etc) in a WYSIWYG text area is a long-standing need that has been challenging in Drupal sites for a while. In Drupal 7, numerous solutions addressed the problem with different (and sometimes competing) approaches. 


Drupal 8 core comes with basic support for embedding images out-of-the-box, but we all know that “ambitious digital experiences” nowadays need to go far beyond that and provide editors with a powerful tool capable of handling any type of media asset, including remote ones such as a relevant Tweet or Facebook post.
 

This is where the powerful combination of Entity Embed + URL Embed comes into play. Predicated on, and extending the foundation established by Drupal core, these two modules were designed to be a standardized solution for embedding any entity or remote URL content in a WYSIWYG editor in Drupal 8.

In this article, you will find:
  • What Drupal 8 core offers out-of-the-box for embedding images inside rich text
  • How to create a WYSIWYG embed button with the Entity Embed module (with some common pitfalls you may encounter along the way)
  • How to embed remote media assets with the URL Embed module (again, with some common pitfalls!)
  • Additional resources on how to extend and integrate your embedding solutions with other tools from the Media ecosystem.
Drupal 8 core embedding

Drupal 8 has made a big step from Drupal 7 and included the CKEditor WYSIWYG library in core. It also comes with an easy-to-use tool to embed local images in your text.

undefined

But what if the content you want to embed is not an image?

I am sure you have come across something like this before:

undefined

or this:

undefined

These are all examples of non-image media assets that needed to be embedded as well. So how can we extend Drupal 8 core’s ability to embed these pieces of content in a rich-text area? 

Enter the Entity Embed module. The Entity Embed module allows any Drupal entity to be embedded within a text area using a WYSIWYG editor.

 The module doesn't care what you embed, as long as that piece of content is stored in Drupal as a standard entity. In other words, you can embed nodes, taxonomy terms, comments, users, profiles, forms, media entities, etc. all in the same way and using a standard workflow.

In order to limit the length of this article, we will be working only with entities provided by Drupal core, but you should definitely try the Media Entity module, which can take your embedding capabilities to a whole new level (more on this at the end of this article).

Basic installation and configuration

Create an embed button

After downloading and installing the Entity Embed module and its dependencies, navigate to 

Configuration -> Content Authoring -> Text editor embed buttons

or go directly to /admin/config/content/embed and click “Add embed button”

undefined

You will see that there is already a button on this page called “Node,” which was automatically created upon installation. For the purposes of the example, we will create another button to embed nodes, but you can obviously use and modify the provided one on your site if you wish.

As an example, we’ll create a button to embed a node into another node, simulating a “Related article” scenario in a news article.

undefined

The config options you will find on this page are:

  • Label: Give your button a human-readable name and adjust its machine name if needed
  • Embed type: If you only have the Entity Embed module enabled, “Entity” is your only option here. When you install other modules that also extend the Embed framework, other options will be available.
  • Entity type: Choose here the type of the entity you will be embedding with this button. In our case, we choose “Content” in order to be able to embed nodes.
  • Content type: We can optionally filter the entity bundles (aka “Content types” for nodes) that the user will be allowed to choose from. In our case we only want “Articles”.
  • Allowed Entity Embed Display plugins: Choose here the display plugins that will be available when embedding the entity. This means that when an editor chooses an entity to embed, they will be asked how this entity should be displayed, and the options the user sees will be restricted to the selections you make here. In our case, we only want the editor to be able to choose between a simple “Label” pointing to the content, or a “Teaser” visualization of the node (which uses the teaser viewmode). More on this later.
  • Entity browser: In our example, we won’t be using any, but you should definitely try the integration of Entity Embed with Entity Browser, making the selection of the embedded entities much easier!
  • Button icon: You can optionally upload a custom icon to be shown on the button. If left empty, the letter “E” will be used.

Once we’ve created our button, it’s time to add it to the WYSIWYG toolbar.

Entity Embed: WYSIWYG configuration

Navigate to:

Configuration -> Content authoring -> Text formats and editors

or go directly to /admin/config/content/formats and click “configure” on the format you want to add the button to. In our example, we are going to add it to the “Basic HTML” text format.

Step 1: Place the button in the active toolbar

undefined

Step 2: Mark the checkbox “Display embedded entities”

undefined

Step 3: Make sure the special tags are whitelisted

If you are also using the filter “Limit allowed HTML tags and correct faulty HTML” (which you should), it’s important to make sure that the tags used by this module are allowed by this filter. Scroll down a little bit and verify that the tags:

<drupal-entity data-entity-type data-entity-uuid data-entity-embed-display data-entity-embed-display-settings data-align data-caption data-embed-button>

are listed in the “Allowed HTML tags” text area.

undefined

If you are using a recent version of the module, this should be automatically populated as soon as you click on “Display embedded entities”, but it doesn’t hurt to verify it was done correctly.

Common pitfall

If you are embedding images and you want to use “Alt” and “Title” attributes, you probably want to fine-tune the allowed tags to be something like:
<drupal-entity data-entity-type data-entity-uuid data-entity-embed-display data-entity-embed-display-settings data-align data-caption data-embed-button alt title>

Instead of the default value provided.

Common pitfall

If you have the filter “Restrict images to this site” active (which comes activated by default in Drupal core), you will probably want to reorder the filters so that “Display embedded entities” comes after the filter “Restrict images to this site”. If you don’t do this, when you embed some image entities you may end up with something like:

undefined All set, ready to embed some entities! 

Your editors can now navigate to any content form that uses the “Basic HTML” text format and start using the button!

Videos require iframe browser support.

Common pitfall

After this issue got in, the module slightly changed the way it manages the display plugins for viewing the embedded entity. As a result, the entity_reference formatter (“Rendered entity”) that many people are used to from other contexts is not available right away, and instead all non-default viewmodes of the entity type are shown directly as display options

undefined

However, for an entity without custom viewmodes, you won’t have the “Full” (or “default”) viewmode available anymore. There is still some discussion happening about how to ensure the best user experience for this issue, if you want to know more about it or jump in and help, you can find more information herehere and here.

Quick and easy solution for remote embedding with URL Embed


A sister-module of Entity Embed, the URL Embed module allows content editors to quickly embed any piece of remote content that implements the oEmbed protocol. 


Nice tech terms, but in practice what does that mean? It means that any content from one of the sites below (but not limited to these) can be embedded:

  • Deviantart
  • Facebook
  • Flickr
  • Hulu
  • IFTTT
  • Instagram
  • National Film Board of Canada
  • Noembed
  • Podbean
  • Revision3
  • Scribd
  • SlideShare
  • SmugMug
  • SoundCloud
  • Spotify
  • TED
  • Twitter
  • Ustream
  • Viddler
  • Vimeo
  • YouTube

Under the hood, the module leverages the awesome Embed open-source library to fetch the content from the remote sites, and uses the same base framework as Entity Embed to create embed buttons for this type of content. As a result, the site builder has a very similar workflow when configuring the WYSIWYG tools, which can then be used by the content editor in a very intuitive way. Let’s see how all that works.

Install the module and create a URL Embed button

As usual, the first step is to enable the module itself, along with all its dependencies.

Common pitfall

This module depends on the external Embed library. If you are using Composer to download the Drupal module (which you should), Composer will do all the necessary work to fetch the dependencies for you. You can also install only the library itself using Composer if you want. To do that just run "composer require embed/embed". Failing to correctly install the library will prevent the module from being enabled, with an error message such as:

undefined

Once successfully enabled, the “Embed button” concept here is exactly the same as in the previous example, so all we need is to go back to /admin/config/content/embed .

undefined

The module already creates a new button for us, intended for embedding remote content. URL buttons have no specific configuration except for name, type and icon, so we’ll skip the button configuration part.

URL Embed: WYSIWYG Configuration

Similarly to what we did with the Entity button, we need to follow the same steps here to enable the URL Embed button on a given text format:

  • Step 1: Place the button in the active toolbar
  • Step 2: Mark the checkbox “Display embedded URLs
  • Step 3: Make sure the special tags are whitelisted. (Note that here the tags won’t be automatically populated, you need to manually introduce <drupal-url data-*> to the “Allowed HTML tags” text area.)
  • Step 4: (Optional) If you want, you can also mark the checkbox “Convert URLs to URL embeds”, which will automatically convert URLs from the text into embedded objects. If you do this though, make sure you reorder the filters so that “Display embedded URLs” is placed after the filter “Convert URLs to URL embeds
 All done, go embed some remote content! Videos require iframe browser support.

Common pitfall

Please bear in mind that while the Embed library can deal with many remote websites, it won’t do magic with providers that don’t implement the oEmbed protocol in a consistent way! Currently the module will only render the content of providers that return something inside the code property. If you are trying to embed some remote content that is not appearing correctly on your site, you can troubleshoot it by going to the URL: https://oscarotero.com/embed3/demo and trying the same URL there. If there is no content inside the code property, this URL unfortunately can’t be embedded in Drupal using URL Embed. Once this issue is complete, this will be less of an issue because the validation will prevent the user from entering an invalid URL.

Get even more by integrating with other Media solutions

There are at least two important ways you can extend the solutions indicated here, to achieve an even more powerful or easy-to-use editorial experience. Those approaches will be discussed in detail in upcoming articles, but you can have a brief idea about them below, in case you want to start learning about them right away.

Allow content to be selected through Entity Browsers


In the previous example, the content being embedded (in this case a referenced node) was selected by the editor using an autocomplete field. This is a very basic solution, available out-of-the-box for any referenced entity in Drupal core, but it does not provide the ultimate user experience we would expect from a modern CMS. The good news: it’s an easy fix. Plug any Entity Browser you may have on your site to any embed button you have created. 

Going over the configuration of Entity Browsers is beyond the scope of this article, but you can read more at the official documentation, or directly by giving it a try, using one of the pre-packaged modules like File Entity Browser, Content Browser or Media Entity Browser.

Common pitfall

If you are using an Entity Browser to select the content to be embedded, make sure your browser is configured to have a display plugin of type “iFrame.” The Embed options already appear in a modal window, so selecting an Entity Browser that is configured to be shown in a modal window won’t work.

Use the Media Entity module to deal (also) with remote media assets

The URL Embed module is a handy solution to allow your site editors to embed remote media assets in a quick-and-easy way, but what if:

  • you want to standardize your workflow for managing local and remote media assets?
  • you want to have a way to re-use content that was already embedded in other pieces of content?
  • etc.

A possible alternative that would solve all those needs is to standardize how Drupal sees all your media assets. The Media Entity approach means that you can “wrap” both local and remote media assets in a special “media" entity type, and as a result, the Entity Embed could be used with any type of asset, once all of them are Drupal entities regardless of their real storage.

In Conclusion

Hopefully, you find this a useful tutorial. I can strongly recommend this “Media Entity” approach, because it is being partly moved into Drupal core, which will result in an even stronger foundation for how Drupal sites will handle media assets in the near future.

Acknowledgements

Thanks to Seth Brown, David Burns and Dave Reid for their help with this article.

Other articles in this series

Image credit: Daian Gan

Categories: Blogs

Tameesh Biswas | Blog: GSoC17 : Client Side File Crypto : Week 3

June 21, 2017 - 7:38am
GSoC17 : Client Side File Crypto : Week 3

This post summarises my third week of coding with Drupal for Google Summer of Code 2017.

Reshaping the REST API

tameeshb Wed, 06/21/2017 - 16:08 Tags GSoC Google Summer of Code 2017 Drupal Drupal Blog
Categories: Blogs

Flocon de toile | Freelance Drupal: Do I have to wait for Drupal 9 for my web project?

June 21, 2017 - 7:00am

In a previous post, we saw the Drupal 8's new policies for versioning, support and maintenance for its minor and major versions. This policy has evolved somewhat since the last DrupalCon Baltimore conference in April 2017. And this evolution of Drupal's strategy deserves a little attention because it can bring new light to those who hesitate to migrate their site on Drupal 8. Or those who are wondering about the relevance of launching their web project on Drupal 8.

Categories: Blogs

Dropsolid: Drupal 8 and React Native

June 21, 2017 - 6:57am
21 Jun Drupal 8 and React Native Niels A Drupal 8 Tech Drupal

One day you might wake up with the next big idea that will shake the world in the most ungentle way. You decide to build an app, because you’ll have full access to all features of the device that you want your solution to work on. But then it dawns on you: you will actually need to build multiple apps in completely different languages while finding a way for them to serve the same content...

Then you start to realise that you won’t be able to step into the shoes of the greats, because web technology is holding you back. Fortunately, Drupal 8 and React Native are here to save your day - and your dream!

In this blog post you'll read how you can leverage Drupal 8 to serve as the back-end for your React Native app. 

First, however, a quick definition of what these technologies are:

  • Drupal is an open source content management system based on PHP.
  • React Native is a framework to build native apps using JavaScript and React.

If you want to read more about Drupal 8 or React Native, you're invited to check the sources at the bottom of this article.
 

Why React Native?

There are a myriad of front-end technologies available to you these days. The most popular ones are Angular and React. Both technologies allow you to build apps, but there is a big difference in how the apps will be built.

The advantage of employing React Native is that it lets you build an app using JavaScript, while converting the JavaScript into native code. In contrast, Angular or Ionic allow you to create a hybrid app, which basically is a website that gets embedded in a web view. Although the benefit here is that you're able to access the native features of a device.

In this case, we prefer React Native, because we want to build iOS and Android applications that run natively.
 

Headless Drupal

One of the big buzzwords that's been doing the rounds in the Drupal community lately is 'Headless'. A headless Drupal is actually a Drupal application where the front-end is not served by Drupal, but by a different technology.

You still get the benefits of a top notch and extremely flexible content management system, but you also get the benefits of your chosen front-end technology.

In this example, you'll discover how to set up a native iOS and Android application that gets its data from a Drupal website. To access the information, users will have to log in to the app, which allows the app to serve content tailored to the preferences of the user. Crucial in the current individualized digital world.

 

So this already brings us to our first hurdle. Because we are using a native application, authenticating users through cookies or sessions is not possible. So we are going to show you how to prepare your React Native application and your Drupal site to accept authenticated requests.
 

The architecture

The architecture consists of a vanilla Drupal 8 version and a React Native project with Redux.

The implemented flow is as following:

  1. A user gets the login screen presented on the app.
  2. The user fills in his credentials in the form
  3. The app posts the credentials to the endpoint in Drupal
  4. Drupal validates the credentials and logs the user in
  5. Drupal responds with a token based on the current user
  6. The app stores the token for future use
  7. The app now uses the token for all other requests the app makes to the Drupal REST API.
     
Creating an endpoint in Drupal

First we had to choose our authentication method. In this example, we opted to authenticate using a JWT or JSON web token, because there already is a great contributed module available for it on Drupal.org (https://www.drupal.org/project/jwt).

This module provides an authentication service that you can use with the REST module that is now in Drupal 8 core. This authentication service will read the token that is passed in the headers of the request and will determine the current user from it. All subsequent functionality in Drupal will then use that user to determine if it has permission to access the requested resources. This authentication service works for all subsequent requests, but not for the original request to get the JWT.

The original endpoint the JWT module provides, already expects the user to be logged in before it can serve the token. You could use the ready available basic authentication service, but we preferred to build our own as an example.
 

Authentication with JSON post

Instead of passing along the username and password in the headers of the request like the basic authentication service expects, we will send the username and password in the body of our request formatted as JSON.

Our authentication class implements the AuthenticationProviderInterface and is announced in json_web_token.services.yml as follows:

services: authentication.json_web_token: class: Drupal\json_web_token\Authentication\Provider\JsonAuthenticationProvider arguments: ['@config.factory', '@user.auth', '@flood', '@entity.manager'] tags: - { name: authentication_provider, provider_id: 'json_authentication_provider', priority: 100 }

The interface states that we have to implement two methods, applies and authenticate:

public function applies(Request $request) { $content = json_decode($request->getContent()); return isset($content->username, $content->password) && !empty($content->username) && !empty($content->password); }

Here we define when the authenticator should be applied. So our requirement is that the JSON that is posted contains a username and password. In all other cases this authenticator can be skipped. Every authenticator service you define will always be called by Drupal. Therefore, it is very important that you define your conditions for applying the authentication service.

public function authenticate(Request $request) { $flood_config = $this->configFactory->get('user.flood'); $content = json_decode($request->getContent()); $username = $content->username; $password = $content->password; // Flood protection: this is very similar to the user login form code. // @see \Drupal\user\Form\UserLoginForm::validateAuthentication() // Do not allow any login from the current user's IP if the limit has been // reached. Default is 50 failed attempts allowed in one hour. This is // independent of the per-user limit to catch attempts from one IP to log // in to many different user accounts. We have a reasonably high limit // since there may be only one apparent IP for all users at an institution. if ($this->flood->isAllowed(json_authentication_provider.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) { $accounts = $this->entityManager->getStorage('user') ->loadByProperties(array('name' => $username, 'status' => 1)); $account = reset($accounts); if ($account) { if ($flood_config->get('uid_only')) { // Register flood events based on the uid only, so they apply for any // IP address. This is the most secure option. $identifier = $account->id(); } else { // The default identifier is a combination of uid and IP address. This // is less secure but more resistant to denial-of-service attacks that // could lock out all users with public user names. $identifier = $account->id() . '-' . $request->getClientIP(); } // Don't allow login if the limit for this user has been reached. // Default is to allow 5 failed attempts every 6 hours. if ($this->flood->isAllowed('json_authentication_provider.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) { $uid = $this->userAuth->authenticate($username, $password); if ($uid) { $this->flood->clear('json_authentication_provider.failed_login_user', $identifier); return $this->entityManager->getStorage('user')->load($uid); } else { // Register a per-user failed login event. $this->flood->register('json_authentication_provider.failed_login_user', $flood_config->get('user_window'), $identifier); } } } } // Always register an IP-based failed login event. $this->flood->register('json_authentication_provider.failed_login_ip', $flood_config->get('ip_window')); return []; }

Here we mostly reimplemented the authentication functionality of the basic authorization service, with the difference that we read the data from a JSON format. This code logs the user into the Drupal application. All the extra code is flood protection.

Getting the JWT token

To get the JWT token we leveraged the REST module, and created a new rest resource plugin. We could have used the endpoint the module already provides, but we prefer to create all our endpoints with a version in it. We defined the plugin with the following annotation:

/** * Provides a resource to get a JWT token. * * @RestResource( * id = "token_rest_resource", * label = @Translation("Token rest resource"), * uri_paths = { * "canonical" = "/api/v1/token", * "https://www.drupal.org/link-relations/create" = "/api/v1/token" * } * ) */

The uri_paths are the most important part of this annotation. By setting both the canonical and the weird looking Drupal.org keys, we are able to set a fully custom path for our endpoint. That allows us to set the version of our API in the URI like this: /api/v1/token. This way we can easily roll out new versions of our API and clearly communicate about deprecating older versions.

Our class extends the ResourceBase class provided by the REST module. We only implemented a post method in our class, as we only want this endpoint to handle posts.

public function post() { if($this->currentUser->isAnonymous()){ $data['message'] = $this->t("Login failed. If you don't have an account register. If you forgot your credentials please reset your password."); }else{ $data['message'] = $this->t('Login succeeded'); $data['token'] = $this->generateToken(); } return new ResourceResponse($data); } /** * Generates a new JWT. */ protected function generateToken() { $token = new JsonWebToken(); $event = new JwtAuthIssuerEvent($token); $this->eventDispatcher->dispatch(JwtAuthIssuerEvents::GENERATE, $event); $jwt = $event->getToken(); return $this->transcoder->encode($jwt, array()); }

The generateToken method is a custom method where we leverage the JWT module to get us a token that we can return. 
 
We do not return a JSON object directly. We return a response in the form of an array. This is a very handy feature of the REST module, because you can choose the formats of your endpoint using the interface in Drupal. So you could easily return any other supported format like xml, JSON or hal_json. For this example, we chose hal_json. 

Drupal has some built-in security measures for non-safe methods. The only safe methods are HEAD, GET, OPTIONS and TRACE. We are implementing a non-safe method, so we have to take into account the following things:

  • When the app does a post it also needs to send a X-CSRF-Token in the header to avoid cross site request forgery. This token can be gotten from /session/token endpoint.
  • In case of a POST we also need to set the Content-type request header to “application/hal+json” on top of the query parameter “_format=hal_json”.
Putting things together

The only thing left is to enable our endpoint through the interface that the rest modules provides on /admin/config/services/rest.

As you can see, we’ve configured our token endpoint with our custom json_authentication_provider service and it is available in hal_json and json formats.

Calling the endpoint in our React Native application The login component

Our login component contain two input fields and a button.

this.setState({username})} placeholderTextColor="#FFF" style={styles.input} /> this.setState({password})} style={styles.input} /> this.login({ username: this.state.username, password: this.state.password })} > Get Started

When we click the login button we trigger the login action that is defined in our bindActions function.

function bindActions(dispatch) { return { login: (username, password) => dispatch(login(username, password)), }; }

The login action is defined in our auth.js:

import type { Action } from './types'; import axios from 'react-native-axios'; export const LOGIN = 'LOGIN'; export function login(username, password):Action { var jwt = ''; var endpoint = "https://example.com/api/v1/token?_format=hal_json"; return { type: LOGIN, payload: axios({ method: 'post', url: endpoint, data: { username: username, password: password, jwt: jwt, }, headers: { 'Content-Type':'application/hal+json', 'X-CSRF-Token':'V5GBdzli7IvPCuRjMqvlEC4CeSeXgufl4Jx3hngZYRw' } }) } }

In this example, we set the X-CSRF-token fixed to keep it simple. Normally you would get this first. We’ve also used the react-native-axios package to handle our post. This action will return a promise. If you use the promise and thunk middleware in your Redux Store you can set up your reducer in the following way.

import type { Action } from '../actions/types'; import { LOGIN_PENDING, LOGOUT} from '../actions/auth'; import { REHYDRATE } from 'redux-persist/constants'; export type State = { fetching: boolean, isLoggedIn: boolean, username:string, password:string, jwt: string, error: boolean, } const initialState = { fetching: false, username: '', password: '', error: null, } export default function (state:State = initialState, action:Action): State { switch (action.type) { case "LOGIN_PENDING": return {...state, fetching: true} case "LOGIN_REJECTED": return {...state, fetching: false, error: action.payload} case "LOGIN_FULFILLED": return {...state, fetching: false, isLoggedIn: true, jwt:action.payload.data.token} case "REHYDRATE": var incoming = action.payload.myReducer if (incoming) return {...state, ...incoming, specialKey: processSpecial(incoming.specialKey)} return state default: return state; } }

The reducer will be able to act on the different action types of the promise:

  • LOGIN_PENDING: Allows you to change the state of your component so you could implement a loader while it is trying to get the token.
  • LOGIN_REJECTED: When the attempt fails you could give a notification why it failed.
  • LOGIN_FULFILLED: When the attempt succeeds you have the token and set the state to logged in.

So once we had implemented all of this, we had an iOS and Android app that actually used a Drupal 8 site as it main content store.

Following this example, you should be all set up to deliver tailored content to your users on whichever platform they may be.

The purpose of this article was to demonstrate how effective Drupal 8 can be as a source for your upcoming iOS or Android application.
 

Useful resources:

 

More articles by our Dropsolid Technical Leads, strategist and marketeers? Check them out here.

Categories: Blogs

Wunderkraut Sweden Blog: Setup PHPStorm to run your Behat tests with a vagrant box

June 21, 2017 - 4:04am
In order to run Behat test from PHPStorm we need to setup our IDE in a certain way because our IDE is on the host and the Behat tool is running inside our vagrant VM, so we will need to use a remote interpreter, let's see how we can do it: Configure the PHP remote interpreter: Go to settings/preferences and browse to Language and frameworks > PHP At CLI interpreter we may have by default, so we can open the CLI interpreters dialog window from the ... icon. In that dialog we can add a new interpreter clicking in the + icon. In our case we can add a 'From Docker, Vagrant, VM, Remote' and we will get different options to setup the connection to the remote interpreter. Change the name of the remote interpreter and check the box 'Visible only for this project'. We can use Vagrant and… Read More
Categories: Blogs

Elevated Third: Recorded Webinar: A Decoupled Drupal Story

June 20, 2017 - 4:30pm
Recorded Webinar: A Decoupled Drupal Story Recorded Webinar: A Decoupled Drupal Story Tue, 06/20/2017 - 13:30

We teamed up with Acquia to present “A Decoupled Drupal Story: Powdr Gives Developers Ultimate Flexibility To Build Best CX Possible.” The webinar aired in June but you can view the recording here anytime.

As the internet and web-connected devices continue to evolve, so do the needs to develop and render content. Given today’s rate of change, organizations are using decoupled architecture to build applications - giving them flexibility to accommodate any device or experience.

In this session, we’ll cover Powdr, a ski resort holding company. To give its developers the freedom to use the right front-end tools needed for any given use case, Powdr built its 17 ski resort websites on one decoupled Drupal platform. Join Elevated Third, Hoorooh Digital and Acquia to learn:

  • How a custom Drupal 8 solution cut implementation time in half vs Drupal 7

  • The ease in which Drupal 8’s internal REST API can be extended to fit a client's needs

  • The details of handling non-Drupal application routing on Acquia's servers

 

If you are considering a decoupled Drupal implementation, let’s talk.

Categories: Blogs

Pages