├── 2018-10-22-all-exercises.pdf
├── README.md
├── clear-cache.md
├── exercise_02-add-content.md
├── exercise_03-contrib-themes.md
├── exercise_04-dot-info.md
├── exercise_05-libraries.md
├── exercise_06-intro-to-twig.md
├── exercise_07-twig-new-region.md
├── exercise_08-twig-dot-syntax.md
├── exercise_09-twig-classes.md
├── exercise_10-twig-filters.md
├── exercise_11-twig-block.md
├── exercise_12-twig-include-svg.md
├── exercise_13-preprocess.md
├── exercise_14-new-template-suggestions.md
├── exercise_15-preprocess-add-classses.md
├── exercise_16-form-alter.md
├── exercise_17-responsive.md
├── exercise_18-theme-settings1.md
├── exercise_19-theme-settings2.md
├── local.services.yml
└── settings.local.php
/2018-10-22-all-exercises.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chapter-three/drupal-8-theming/97127208c7720aa54357f1d1b0748ede0bc5ecfe/2018-10-22-all-exercises.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Drupal 8 Theming Training
2 |
3 | ## Description
4 |
5 | Are you struggling with Drupal 8 theming? New to Drupal or Drupal 8?
6 |
7 | Let's take it from the top!
8 |
9 | This hands-on training will take you step-by-step through the process of creating a custom theme in Drupal 8. There will also be time for Drupal Q&A.
10 |
11 |
12 | Along the way, we'll cover:
13 |
14 | - Setting up Your local development environment
15 | - Working with YAML files
16 | - Adding assets responsibly
17 | - Getting, modifying and display Drupal data in the template
18 | - Cool Twig tips and Tricks
19 | - Preprocessing template functions and hooks
20 | - Basic OOPHP principles
21 | - Kint, theme_debug and other changes to debugging
22 | - Leveraging new Drupal 8 site building paradigms to make theming easier
23 | - Where to find help
24 |
25 |
26 | ## Notes:
27 |
28 | * All terminal commands are run from the Drupal root.
29 |
30 | * `$` indicates a prompt. You do not need to type it into the terminal window.
31 |
32 | * `MYDRUPAL` refers to the Drupal root directory or base URL.
33 |
34 | * You'll have an easier time if you configure your editor to use spaces instead of tabs.
35 |
36 | * Feel free to ask any of the "Questions you may have ..." someone probably asked the question before!
37 |
38 | * If you're using Lando, `composer` becomes `lando composer`, `drush` becomes `lando drush`.
39 |
40 | * See a mistake or typo? Submit a pull request on Github!
41 |
42 | * Common hiccups are:
43 | * Syntax errors
44 | * Too many or not enough spaces in .yml files
45 | * Cache not cleared
46 | * PHP memory limit not high enough (>= 256)
47 | * settings.php
48 | - `ini_set('memory_limit', '512M');`
49 | * php.ini
50 | - `'memory_limit' = '512M'`
51 |
52 | ## Basic terminal commands used.
53 |
54 | Change Directory.
55 |
56 | ```cd ```
57 |
58 | Create File.
59 |
60 | ```touch``` (`new-item` on Windows PowerShell)
61 |
62 | Create Folder.
63 |
64 | ```mkdir```
65 |
66 | Move file from one location to another.
67 |
68 | ```mv```
69 |
70 | Copy file to a new location.
71 |
72 | ```cp``` (`copy` on Windows PowerShell)
73 |
74 |
75 | ## Final Folder Structure
76 |
77 | ```
78 | MYDRUPAL/themes/custom
79 | └── acme
80 | ├── acme.breakpoints.yml
81 | ├── acme.info.yml
82 | ├── acme.libraries.yml
83 | ├── acme.settings.yml
84 | ├── acme.theme
85 | ├── css
86 | │ ├── css-stuff-print.css
87 | │ ├── css-stuff.css
88 | │ └── custom-widget.css
89 | ├── images
90 | │ └── mysvg.svg
91 | ├── js
92 | │ └── custom-widget.js
93 | ├── templates
94 | │ ├── block
95 | │ │ └── block--system-powered-by-block.html.twig
96 | │ ├── html
97 | │ │ └── html.html.twig
98 | │ ├── node
99 | │ │ └── node.html.twig
100 | │ └── page
101 | │ └── page.html.twig
102 | └── theme-settings.php
103 | ```
104 |
105 |
106 | ## Exercises
107 |
108 | [Exercise 1 - Local Setup](https://docs.google.com/document/d/1KZsdw7u4KoMo2HZqdWz-1gS8pDeV5JhWbIxGe-dt97g/edit?usp=sharing)
109 |
110 | [Exercise 2 - Add Content](exercise_02-add-content.md)
111 |
112 | [Exercise 3 - Contrib Themes](exercise_03-contrib-themes.md)
113 |
114 | [Exercise 4 - Dot Info File](exercise_04-dot-info.md)
115 |
116 | [Exercise 5 - Libraries](exercise_05-libraries.md)
117 |
118 | [Exercise 6 - Intro to Twig](exercise_06-intro-to-twig.md)
119 |
120 | [Exercise 7 - Regions](exercise_07-twig-new-region.md)
121 |
122 | [Exercise 8 - Dot Syntax](exercise_08-twig-dot-syntax.md)
123 |
124 | [Exercise 9 - Twig Classes](exercise_09-twig-classes.md)
125 |
126 | [Exercise 10 - Twig Filters](exercise_10-twig-filters.md)
127 |
128 | [Exercise 11 - Twig Blocks](exercise_11-twig-block.md)
129 |
130 | [Exercise 12 - Include SVG](exercise_12-twig-include-svg.md)
131 |
132 | [Exercise 13 - Preprocess Function](exercise_13-preprocess.md)
133 |
134 | [Exercise 14 - Template Suggestions](exercise_14-new-template-suggestions.md)
135 |
136 | [Exercise 15 - Add Classes with PHP](exercise_15-preprocess-add-classses.md)
137 |
138 | [Exercise 16 - Form Alter](exercise_16-form-alter.md)
139 |
140 | [Exercise 17 - Responsive Images](exercise_17-responsive.md)
141 |
142 | [Exercise 18 - Custom Theme Settings 1](exercise_18-theme-settings1.md)
143 |
144 | [Exercise 19 - Custom Theme Settings 2](exercise_19-theme-settings2.md)
145 |
146 | ## Style Guides for Contributors
147 |
148 | ###### Path to files and directories.
149 |
150 | **MYDRUPAL/themes** Path to files and directories.
151 |
152 | ###### Name of file or directory
153 | **node.html.twig**
154 |
155 | ###### Code.
156 |
157 | ```bash
158 | $ cd drupal
159 | ```
160 |
161 | ###### Url
162 | *http://MYDRUPAL/admin/config*
163 |
164 |
165 | ## Done ☺
166 |
--------------------------------------------------------------------------------
/clear-cache.md:
--------------------------------------------------------------------------------
1 | # Clearing the Registry
2 | Throughout these exercises you'll be asked to clear cache or registry in order to see changes.
3 | Ways to clear registry (aka "Clear Cache"):
4 |
5 | 1. use $ drush cr in the terminal
6 | 2. go to Configuration > Performance and Clear All Caches.
7 | 3, with admin_toolbar_tools enabled, hover over the Drupal icon and choose Flush all Caches.
8 |
9 | Done ☺
10 |
--------------------------------------------------------------------------------
/exercise_02-add-content.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 2:
4 |
5 | ## Auto Generate Content
6 |
7 | You can easily create one or two items of content in Drupal, but what if you need to test out what 50 nodes look like in a view? What if you need to know how the page is going to look with a couple dozen comments all at different levels? Are you going to create each one of those items by hand? Hopefully not. Luckily, we have a handy module that has already been ported to D8 and is (mostly) able to take care of this work for us. Enter the "Devel" and "Devel Generate" module.
8 |
9 | 1. Enable the **Devel generate** module if you have not already.
10 |
11 | ### For vocabularies and taxonomy terms
12 | 1. Click "Configuration" in the Admin menu.
13 | 2. In the `Development` group select `Generate vocabularies`.
14 | 1. Number of vocabularies?: `2`
15 | 2. Maximum number of characters in vocabulary names: `20`
16 | 3. Click the **Generate** button.
17 | 3. Click "Configuration" in the Admin menu
18 | 4. In the `Development` group select `Generate terms`.
19 | 1. Vocabularies: **Select "Tags" and one other vocabulary**
20 | 2. Number of terms?: `10`
21 | 2. Maximum number of characters in vocabulary names: `15`
22 | 3. Click the **Generate** button.
23 |
24 |
25 | ### For content
26 |
27 | 1. Click "Configuration" in the Admin menu.
28 | 2. In the `Development` group select `Generate content`.
29 | 1. Select Content Types `Article` and `Basic Page`
30 | 2. How many nodes would you like to generate?: `25`
31 | 3. How far back in time should the nodes be dated?: `1 year`
32 | 4. (If comments enabled) Maximum number of comments per node: `5`
33 | 4. Maximum number of words in titles: `10`
34 | 5. Leave the rest at their default values
35 | 3. Click the **Generate** button.
36 |
37 |
38 | ### For users and menus
39 | 1. Feel free to generate users and menus using the same procedure.
40 |
41 | ## Questions you may have...
42 | + Can I generate content from the command line?
43 |
44 | ## Done ☺
45 | [Exercise 3 - Contrib Themes](exercise_03-contrib-themes.md) is next!
--------------------------------------------------------------------------------
/exercise_03-contrib-themes.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 3:
4 |
5 | ## Contrib themes
6 |
7 | This is where most themers will start their career. Let someone else do the work for you and download something generic that can be modified to fit your needs. Contributed themes can be a great resource for learning new techniques and functionality, which you can implement in your custom themes.
8 |
9 | You can find themes at [http://drupal.org/project/themes](http://drupal.org/project/themes), as well as some external sites. Make sure the theme is compatible with Drupal 8.
10 |
11 | If you run into problems, check the theme's issue queue and search the forums. If your problem hasn't already been addressed, post a question, and someone will try to help you out.
12 |
13 |
14 | ## Install our first contrib...
15 | 1. Download and add the latest recommended release of *Radix*: http://drupal.org/project/radix
16 |
17 | Remember if you're using Lando, `composer` becomes `lando composer`; please see the [README](https://github.com/chapter-three/drupal-8-theming/blob/master/README.md) for more details
18 |
19 | `$ composer require drupal/radix`
20 |
21 | 2. Also, download and add the latest recommended release of the *Mayo* theme: https://www.drupal.org/project/mayo
22 |
23 | `$ composer require drupal/mayo`
24 |
25 | 3. Read `README.txt` inside of the *mayo* folder to know what to expect.
26 |
27 | 4. Enable the *Mayo* theme:
28 | 1. Click on **Appearance** at the top.
29 | 2. Find *Mayo* near the bottom and click **Install and set default**.
30 |
31 | 5. [Clear cache](clear-cache.md).
32 |
33 |
34 | > ### Typical structure of a theme
35 | >**MYTHEME.info.yml** - A theme must contain an .info.yml file to define the theme. Among other things, the .info.yml files define metadata, style sheets, and block regions. This is the only required file in the theme.
36 | >
37 | >**MYTHEME.libraries.yml** - The .libraries.yml file is used to define JavaScript and CSS libraries that can be loaded by the theme.
38 | >
39 | >**MYTHEME.breakpoints.yml** - Breakpoints define where a design changes in response to different devices. While the theme can use this file, its info can also be used by Drupal core to make adjustments to data it sends to the theme.
40 | >
41 | >**MYTHEME.theme** - The .theme file is equivalent to the old Drupal 7 template.php file. It is a PHP file that contains conditional logic and data (pre)processing of the output.
42 | >
43 | >**screenshot.png** - If a screenshot.png file is found in the theme folder, it will be used on the Appearance page. You can also define a screenshot image in .info.yml file.
44 | >
45 | >**logo.svg** - New to Drupal 8, logos are best saved as svg files and placed in the main level of your theme. Logos can also be uploaded at Appearance > Settings.
46 | >
47 | >**css/** or **styles/** - Drupal 8 core themes organize CSS files following the SMACCS style guide. For CSS files to be loaded, they must be defined in your .libraries.yml file. You can override or remove core and module CSS in your themes .info.yml file.
48 | >
49 | >**js/** or **script/** - JavaScript files are stored in the 'js' folder. For a theme to load JavaScript files, they must be defined in .libraries.yml file.
50 | >
51 | >**img/** or **images/** - It is good practice to store images in the 'images' subfolder.
52 | >
53 | >**templates/** - Templates provide HTML markup and some presentation logic. It is customary to place template files into subcategory folders, such as **templates/node/** for node based templates.
54 |
55 | ## ... then we configure our theme
56 | 1. Revisit the Appearance page and click **Settings** under the _Mayo_ theme.
57 | 2. Play with some of the configuration settings.
58 | 3. Try to complete the following tasks:
59 | 1. Tweak the color scheme by utilizing the color wheel.
60 | 2. Change the base font selection to be `Verdana, Geneva, Arial, ...`
61 | 3. Set both sidebars to appear on the left side on big screens.
62 | 3. Set sidebar to use round corners.
63 |
64 | 4. Many settings are theme-specific. Visit the settings pages of other themes, like *Seven* to compare.
65 |
66 |
67 | ## Questions you may have...
68 | + How do I choose a theme?
69 |
70 | ## Done ☺
71 | Woo hoo! Time for [Exercise 4 - Dot Info File](exercise_04-dot-info.md)! :)
72 |
73 |
--------------------------------------------------------------------------------
/exercise_04-dot-info.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 4:
4 |
5 | ## The .info.yml file
6 |
7 | To create a new theme for Drupal 8, the only real requirement is to make sure you have implemented the .info.yml file (YaML, like Camel). Drupal 8 runs off of YaML files much the way that Drupal 7 ran off of .info files.
8 |
9 | As long as you have your .info.yml file in place with a few keys in place, you have a theme (although if that's all you have in place, it's not going to be a pretty theme).
10 |
11 | ### Create the theme folder and "YaML" file
12 | 1. Locate the **MYDRUPAL/themes** folder in your Drupal installation. In Drupal 8, Contrib and Custom modules and themes, are saved at the root level **MYDRUPAL/modules** and **MYDRUPAL/themes** folders.
13 |
14 |
15 | 2. Create a new folder called `custom` and inside it a folder called `acme`
16 |
17 | ```bash
18 | $ cd MYDRUPAL
19 | $ mkdir themes/custom
20 | $ mkdir themes/custom/acme
21 | ```
22 | 3. In that folder create a new file. It should be called **acme.info.yml**
23 |
24 | ```bash
25 | $ touch themes/custom/acme/acme.info.yml
26 | ```
27 | 4. Add the following lines to that file:
28 |
29 | ```
30 | name: Acme
31 | description: My first Drupal 8 theme.
32 | type: theme
33 | base theme: classy
34 | core: 8.x
35 | version: VERSION
36 |
37 | regions:
38 | header: Header
39 | primary_menu: 'Primary menu'
40 | secondary_menu: 'Secondary menu'
41 | breadcrumb: Breadcrumb
42 | help: Help
43 | hightlighted: Highlighted
44 | content: Content
45 | sidebar_first: 'Left sidebar'
46 | sidebar_second: 'Right Sidebar'
47 | footer: Footer
48 | page_top: 'Page top'
49 | page_bottom: 'Page Bottom'
50 | ```
51 |
52 | 5. Clear cache.
53 |
54 | 6. Go to /admin/appearance and under the Acme theme choose `Install and set as default`.
55 |
56 | 7. Go to the home page and observe your beautiful new theme.
57 |
58 |
59 | >## Things we might find in a .info.yml file
60 | The following keys are items that we will often find in a `*.info.yml` file. Some are optional; some are required. These keys provide metadata about your theme and define some of the basic functionality.
61 |
62 | >`name` **Required**
63 | The human-readable name will appear on the Appearance page, where you can activate your theme.
64 | >
65 | >`description` **Required** The description is displayed on the Appearance page.
66 | >
67 | >`type` **Required** The type key indicates the type of extension, e.g., module, theme or profile. For themes this should always be set to `theme`.
68 | >
69 | >`base theme` The theme can inherit the resources from another theme by defining it as a base theme. Not declaring this, will default to using "Stable" as the base theme.
70 | >
71 | >`core` **Required** The core key specifies the version of Drupal core that your theme is compatible with.
72 | >
73 | >`version` For modules hosted on drupal.org, the version number will be filled in by the packaging script. You should not specify it manually, but leave out the version line entirely.
74 | >
75 | >`regions` Regions are declared as children of the regions key. You are required to have a content region. The regions we just declared are also the default regions that are enabled by core if you do not declare any regions in your .info.yml file.
76 |
77 | ### Other items
78 | >`regions_hidden` will allow you to remove default regions from the output if you don't specifically declare any regions.
79 | >
80 | >`screenshot: IMAGE_NAME.png` With the screenshot key you define a screenshot that is shown on the Appearance page. If you do not define this key, then Drupal will look for a file named 'screenshot.png' or 'screenshot.svg' in the theme folder to display.
81 | >
82 | >`libraries` The libraries key can be used to add asset libraries to all pages where the theme is active. Theme libraries contain CSS and/or javascript. Theme libraries are declared in another type of YaML file (THEME.libraries.yml). We will cover libraries in later exercises.
83 | >
84 | > ```yml
85 | > libraries:
86 | > - THEMENAME/global-styling
87 | > - THEMENAME/LIBRARY-NAME
88 | > ```
89 | >
90 | >`libraries-override` and `libraries-extend` keys can be used to take control over the components of a library, remove items or the complete library, or add additional elements to a library.
91 | >
92 | >`stylesheets-remove` key is used to stop the addition of CSS components for core and contrib modules. However, it is better to do this with `libraries-override`.
93 | >
94 | >```
95 | > stylesheets-remove:
96 | > - core/assets/vendor/normalize-css/normalize.css
97 | > - '@classy/css/components/tabs.css'
98 | > ```
99 | >
100 | >`ckeditor_stylesheets` will allow you to have custom ckeditor styles attached to your theme
101 | >
102 | >`quickedit_stylesheets` will allow you to have custom styles and javascript attached to the quickedit functionality
103 |
104 | ## Questions you may have...
105 | + What happens if I don't declare any regions?
106 | + Why are some regions in `''` and others not?
107 |
108 | ## Done ☺
109 | [Exercise 5 - Libraries](exercise_05-libraries.md) awaits!
110 |
111 |
--------------------------------------------------------------------------------
/exercise_05-libraries.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 5:
4 |
5 | ## Drupal asset libraries: adding CSS and JavaScript to your site
6 |
7 | In Drupal 8, "Asset Libraries" are used for loading stylesheets (CSS) and JavaScript (JS). The libraries system that you use in your theme is the same framework used by modules and core. Asset libraries can contain one or more CSS assets, one or more JS assets, and one or more JS settings.
8 |
9 | Drupal follows this high-level principle: CSS and JavaScript are only loaded if you tell Drupal it should load them. Drupal has stopped assuming that it should load all assets on all pages, because this is bad for front-end performance.
10 |
11 | The biggest difference from Drupal 7 is when it comes to JavaScript loaded on pages. By default Drupal doesn't need JavaScript on most pages that anonymous users can see. This means that jQuery is not automatically loaded on all pages anymore. If your theme does need it, you can declare it in one of your theme's libraries, and it will be added to every page.
12 |
13 | ### The general process for adding CSS and JavaScript
14 |
15 | The basic process breaks down into 3 steps
16 |
17 | >1. Save the CSS or JS to a file.
18 | >2. Define a "library" in a `*.libraries.yml` file, which contains a reference to the CSS and/or JS files.
19 | >3. "Attach" the library to a render array in a hook using the `#attached` attribute, by including it in a template, or add the library as a dependency to the theme's `*.info.yml` file.
20 |
21 |
22 | ## Create a library
23 |
24 | In the following steps, we will create a libraries.yml file, declare our library some CSS files and create the CSS files in our theme.
25 |
26 | 1. Navigate to your theme's root directory
27 |
28 | ```bash
29 | $ cd MYDRUPAL
30 | ```
31 | 2. Create a file called **acme.libraries.yml** and open that file in your preferred code editor.
32 |
33 | ```bash
34 | $ touch themes/custom/acme/acme.libraries.yml
35 | ```
36 |
37 | 3. Add the following code to that file:
38 |
39 | ```yml
40 | css-stuff:
41 | version: VERSION
42 | css:
43 | theme:
44 | css/css-stuff.css: {}
45 | css/css-stuff-print.css: { media: print }
46 | //fonts.googleapis.com/css?family=Abhaya+Libre|Open+Sans: { type: external }
47 |
48 | ```
49 |
50 | We just declared our library called `css-stuff`. We can give it any name we want as long as another module or theme hasn't declared it. A best practice for theme libraries is to use some word(s) to describe what the library will do. A name like `global-styles` would be more accurate for this library, but I'm a free spirit and I like to run with scissors.
51 |
52 | 4. Navigate back to your theme's root directory, and create a folder called **"css"** (if one doesn't already exist).
53 |
54 | ```bash
55 | $ mkdir themes/custom/acme/css
56 | ```
57 |
58 | 5. Create two files in that directory. One called **css-stuff.css** and one called **css-stuff-print.css**
59 |
60 |
61 | ```bash
62 | $ touch themes/custom/acme/css/css-stuff.css
63 | $ touch themes/custom/acme/css/css-stuff-print.css
64 | ```
65 |
66 | 6. Open them both and add any high level css you want. For **css-stuff.css** you could add:
67 |
68 | ```css
69 | body {
70 | background-color: #e7e7e7;
71 | font-family: 'Open Sans', serif;
72 | padding: 30px;
73 | }
74 |
75 | h2 a {
76 | background-color: #0c89af;
77 | color: white;
78 | font-family: 'Abhaya Libre', serif;
79 | padding: 3px;
80 | text-decoration: none;
81 | }
82 |
83 | h4 {
84 | background-color: #3baf8a;
85 | font-size: 1.5em;
86 | padding: 3px;
87 | }
88 | ```
89 | and for **css-stuff-print.css**, you could add:
90 |
91 | ```css
92 | body {
93 | font-family: sans-serif;
94 | color: #888;
95 | }
96 | ```
97 | Go crazy if you want!
98 |
99 | 7. Navigate back to your theme root directory and open your **acme.info.yml** file
100 |
101 | 8. We need to make our theme aware of our newly declared library before anything happens.
102 |
103 | 9. Add the following code to your **acme.info.yml** file
104 |
105 | ```
106 | libraries:
107 | - acme/css-stuff
108 | ```
109 | 10. [Clear cache](clear-cache.md) and you should now see your css styles in place.
110 |
111 | If everything worked, you should see output like the following in the head section in the source code on all pages of your theme.
112 |
113 | ```html
114 | @import url("/themes/acme/css/css-stuff.css?of7sd1");
115 | ...
116 |
119 | ```
120 |
121 | ## Attach Library to a template
122 | In Drupal 8, it is recommended that libraries only be applied when needed. Unless a style is being used on every page, we should use the `attach_library()` function in twig to add JS or CSS.
123 |
124 | 1. Create a folder called **js** in your theme root and add a JavaScript file called **custom-widget.js**
125 |
126 | ```bash
127 | $ mkdir themes/custom/acme/js
128 | $ touch themes/custom/acme/js/custom-widget.js
129 |
130 | ```
131 |
132 | 2. Create a CSS file in the **css** folder called **custom-widget.css**.
133 |
134 | ```bash
135 | $ touch themes/custom/acme/css/custom-widget.css
136 |
137 | ```
138 |
139 |
140 | 3. Add some sample CSS and js to each file:
141 |
142 | **custom-widget.js:**
143 |
144 | ```js
145 | console.log('It Works');
146 | ```
147 |
148 | **custom-widget.css:**
149 |
150 | ```css
151 | article {
152 | background: white;
153 | padding: 10px;
154 | }
155 | ```
156 |
157 | 3. Define your new library in **acme.libraries.yml**
158 |
159 | ```
160 | widget:
161 | version: VERSION
162 | js:
163 | js/custom-widget.js: {}
164 | css:
165 | theme:
166 | css/custom-widget.css: {}
167 | dependencies:
168 | - core/drupal
169 | - core/jquery
170 | ```
171 |
172 | 1. Navigate to **MYDRUPAL/core/themes/classy/templates/content/** and look for **node.html.twig**.
173 |
174 | 2. Make a copy of **node.html.twig** and paste it in at **MYDRUPAL/themes/custom/acme/templates/node**. Then, because you have added a new file, you'll need to clear cache.
175 |
176 | ```bash
177 | $ mkdir themes/custom/acme/templates
178 | $ mkdir themes/custom/acme/templates/node
179 | $ cp core/themes/classy/templates/content/node.html.twig themes/custom/acme/templates/node/node.html.twig
180 | $ drush cr
181 | ```
182 |
183 | 4. Add the following to your theme's new **node.html.twig** near the top
184 |
185 | ```twig
186 | {% if node.bundle == 'article' %}
187 | {{ attach_library('acme/widget') }}
188 | {% endif %}
189 | ```
190 | 6. Verify that **custom-widget.js** and **custom-widget.css** have loaded on the article pages but NOT on basic pages.
191 |
192 | ## Overriding and removing libraries
193 |
194 | ### Removing CSS or javascript
195 |
196 |
197 | If we are using a base theme, it is very possible that some CSS or javascript is coming over from the base theme that we just do not want to use in our theme. Or sometimes there stylesheets coming from core that we just don't want. Even though Drupal tries to follow the rule of only "load stuff only if needed," you may find yourself in a situation where a contrib module or other library is just adding too much. We can remove stylesheets from output at the theme level by using the `stylesheets-remove` key in our *.info.yml* file.
198 |
199 | **The following method is for removing individual CSS or javascript files**
200 |
201 | 1. Take a look at the CSS that is being loaded on your site as a logged in user. Note that **core/assets/vendor/jquery.ui/themes/base/core.css** and
202 | **/core/themes/classy/css/components/breadcrumb.css** are being loaded.
203 |
204 | 2. Navigate to your theme root and open the **acme.info.yml** file.
205 |
206 | 3. Place the following code in your **acme.info.yml** file.
207 |
208 | ```
209 | stylesheets-remove:
210 | - core/assets/vendor/jquery.ui/themes/base/core.css
211 | - '@classy/css/components/breadcrumb.css'
212 | ```
213 |
214 | In this example, we are removing the extra CSS file that core tries to add with one of its JQuery libraries. We are also removing a stylesheet from our parent (base) theme, classy. You probably notice the `@` symbol at the start of the third line. This is a placeholder token. @classy, or @node, or @whatever is the equivalent to `drupal_get_path()` in Drupal 7. Note that `stylesheets-remove` is technically deprecated and will be removed in Drupal 9.
215 |
216 | 4. [Clear cache](clear-cache.md)
217 |
218 |
219 | ### Overriding CSS or javascript
220 |
221 | Sometimes we don't want to remove a file or a library, but we do have a better file for it to use. We can override whole libraries or components of them. Below is an example of libraries-override from drupal.org. You would place this in your *.info.yml file.
222 |
223 | Note: The methods for modifying libraries have changed during the development of Drupal 8. Check [Drupal.org's Theming Guide](https://www.drupal.org/docs/8/theming-drupal-8/adding-stylesheets-css-and-javascript-js-to-a-drupal-8-theme#override-extend) for the latest documentation.
224 |
225 | **This code is just for show.**
226 |
227 | >```
228 | >libraries-override:
229 | > # Replace an entire library.
230 | > core/drupal.collapse: mytheme/collapse
231 | >
232 | > # Replace an asset with another.
233 | > subtheme/library:
234 | > css:
235 | > theme:
236 | > css/layout.css: css/my-layout.css
237 | >
238 | > # Replace a core module JavaScript asset.
239 | > toolbar/toolbar:
240 | > js:
241 | > js/views/BodyVisualView.js: js/views/BodyVisualView.js
242 | >
243 | > # Remove an asset.
244 | > drupal/dialog:
245 | > css:
246 | > theme:
247 | > dialog.theme.css: false
248 | >
249 | > # Remove an entire library.
250 | > core/modernizr: false
251 | >```
252 | >
253 | > **Libraries Extend Example**
254 | >
255 | > ```
256 | > # Extend drupal.user: add assets from classy's user libraries.
257 | > libraries-extend:
258 | > core/drupal.user:
259 | > - classy/user1
260 | > - classy/user2
261 | >```
262 |
263 | ## Questions you may have...
264 | + Why is the word `theme` in the library definition?
265 | + Is `//fonts.googleapis.com/...` a comment?
266 | + Why do some lines start with `-` in .yml files and others don't?
267 |
268 |
269 | ## Done ☺
270 | You're doing great! Proceed to [Exercise 6 - Intro to Twig](exercise_06-intro-to-twig.md)
271 |
272 |
--------------------------------------------------------------------------------
/exercise_06-intro-to-twig.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 6:
4 |
5 | ## Intro to Twig
6 |
7 | Twig is a modern, advanced, templating language for PHP. Twig is also the new templating engine for Drupal 8. It replaces the old and antiquated phptemplate engine. Twig is fast, secure, and incredibly flexible, but one of its best attributes is that it does not allow some of the bad habits in Drupal theming that phptemplate allowed in the past. (I'm referring to stuff like database queries and data preprocessing in the template files. We've all done it at some point, and yes, we are all ashamed.)
8 |
9 | **If debugging is not enabled, please see "Exercise 1"**
10 |
11 | ## Basic Twig
12 |
13 | There are 3 Basic Syntaxes of Twig. Mostly, we will use just 2 of them.
14 |
15 | ###The “Say Something” Syntax: {{ ... }}
16 |
17 | The double-curly-brace (`{{`) is always used to **print** something. If whatever you need to do will result in something being printed to the screen, then you’ll use this syntax. I call this the “say something” tag, because it’s how you “speak” in Twig.
18 |
19 | + Printing a variable
20 |
21 | ```twig
22 | {{ content }}
23 | ```
24 |
25 | ###The “Do Something” Syntax: {% ... %}
26 |
27 | The curly-percent (`{%`) is the other syntax, which we call the “do something” syntax. It’s used for things like **if** and **for** tags as well as other things that “do” something. There are only a handful of things that can be used inside of it. See [Twig’s documentation](https://twig.symfony.com/doc/2.x/) for all Twig functionality. Twig tags can be used inside of a “do something” statement. The only ones you need to worry about now are **if** and **for**. We’ll talk about a bunch more of these later.
28 |
29 | + Running a conditional check (in this case, check if the variable 'logo' is set, if so print the logo)
30 |
31 | ```twig
32 | {% if logo %}
33 | {{ logo }}
34 | {% endif %}
35 | ```
36 |
37 | + Running a loop and printing a value (in this case, for each product item in products array, it'll print out the product item content)
38 |
39 | ```twig
40 | {% for product in products %}
41 |
{{product}}
42 | {% endfor %}
43 | ```
44 |
45 | ###The Comment Syntax: {# ... #}
46 |
47 | There is a third syntax, used for comments: `{#`. Just like with the “say something” and “do something” syntaxes, write the opening {# and also the closing #} at the end of your comments:
48 |
49 | + An example comment
50 |
51 | ```twig
52 | {# This is a comment for you to enjoy :) #}
53 | ```
54 |
55 |
56 | ## What's in a twig template
57 |
58 | 3. Open your theme's **node.html.twig** file in a text editor and add one of the following lines somewhere at the end of the twig template `{{ dump(date) }}` or `{{ kint(content_attributes) }}`
59 |
60 | 4. Visit a node page, and lets see what it gives us.
61 |
62 | ## Use Twig
63 | ### Print out the bundle type for the node:
64 |
65 | 1. Inspect the **node.html.twig** template, review the comments and variables.
66 |
67 | 3. Add a line to output the the node's bundle type before ``
68 | ``:
69 |
70 | ```twig
71 |
{{ node.bundle}}
72 | ```
73 |
74 | 4. Clear cache, visit (or refresh) a node.
75 |
76 | ##Questions you may have
77 |
78 | + Can I modify a variable through Twig?
79 |
80 |
81 | ## Done ☺
82 |
83 | Get your [Exercise 7 - Regions](exercise_07-twig-new-region.md) on.
84 |
--------------------------------------------------------------------------------
/exercise_07-twig-new-region.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 7:
4 |
5 | ## Adding a new region
6 |
7 | The client wants a new region for special messages at the top of the footer, so we’ll give them a new region called `Footer top`. Eventually, it would be great to style this region to look a little nicer, but for now, lets just put the content in place.
8 |
9 | 1. Update **acme.info.yml** to include the new Footer top region:
10 |
11 | ```
12 | regions:
13 | ...
14 | footer_top: 'Footer top'
15 | ...
16 | ```
17 |
18 | 2. Now that Drupal knows that a new region is available, we need to print that region to the page by modifying **page.html.twig**.
19 | 3. Navigate to the Classy theme in **MYDRUPAL/core/themes/classy/templates/layout** and locate **page.html.twig**.
20 | 4. Make a copy of **page.html.twig** and put it in the **acme** theme folder. We like to keep it organized, so we'll put it in **MYDRUPAL/themes/custom/acme/templates/page**.
21 |
22 | ```bash
23 | $ cd MYDRUPAL
24 | $ mkdir themes/custom/acme/templates/page
25 | $ cp core/themes/classy/templates/layout/page.html.twig themes/custom/acme/templates/page/page.html.twig
26 | ```
27 |
28 |
29 | 5. Edit your theme’s new version of **page.html.twig**:
30 | Look for and copy the following lines of code around line 81:
31 |
32 | ```twig
33 | ...
34 |
35 | {% if page.footer %}
36 |
39 | {% endif %}
40 |
41 | ...
42 | ```
43 |
44 | 6. Above this footer callout code, paste your copy.
45 | 7. In your pasted code, Replace `footer` with `footer_top`, It should look like this when you’re finished:
46 |
47 |
48 | ```twig
49 | ...
50 |
51 | {% if page.footer_top %}
52 |
55 | {% endif %}
56 |
57 | {% if page.footer %}
58 |
61 | {% endif %}
62 |
63 | ...
64 | ```
65 |
66 | 8. Flush your cache and try putting a block in the new region.
67 |
68 | ## Questions you may have...
69 | + Can I add more regions? Can I remove ones I don’t need?
70 | + How do I style my new region?
71 |
72 |
73 | ## Done ☺
74 | Sometimes you feel like a nut; sometimes you [Exercise 8 - Dot Syntax](exercise_08-twig-dot-syntax.md).
75 |
76 |
--------------------------------------------------------------------------------
/exercise_08-twig-dot-syntax.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 8:
4 |
5 | ## Manipulating variables in the template
6 |
7 | Look at the comments at the top of the **node.html.twig** template. The comments detail the variables that are available to this particular template. In twig, we can use certain filters to manipulate the output of variables.
8 |
9 | ### Use `kint()` to inspect the content variable.
10 |
11 | 1. Add `{{ kint(content) }}` to the bottom of your **node.html.twig** field.
12 |
13 | 2. Go to an Article node page.
14 |
15 | 3. Inspect the content variable. Note that body, field_tags, and field_image are available.
16 |
17 | _Make sure your [php memory limit](https://www.drupal.org/docs/7/managing-site-performance-and-scalability/changing-php-memory-limits) is high (>= 256), or you may see a white screen of death._
18 |
19 |
20 | ### Print content without certain fields.
21 |
22 | 1. Delete the kint statement.
23 |
24 | 1. Verify that the article you're looking at has Tags.
25 |
26 | 1. In **node.html.twig**, change ```{{ content }}``` to ```{{ content|without('field_tags') }}```
27 |
28 | 2. View an article node page. Now all fields are printed except for the tags field.
29 |
30 | ### Print fields individually.
31 |
32 | 1. Find ```{{ content | without('field_tags') }} ``` from the previous step and change it to ```{{ content | without ('field_tags', 'field_image') }}```
33 |
34 | This removes the tags and the image fields.
35 |
36 |
37 | 2. Add the following directly above `{{ content | without ('field_tags', 'field_image') }}`.
38 |
39 | ```twig
40 |
44 | ```
45 |
46 | You should now see your Tags and Image fields inside a div with class `sidebar`.
47 |
48 | 3. Add a little styling to **css-stuff.css** to get the full effect.
49 |
50 | ```css
51 | .node__content {
52 | display: grid;
53 | grid-template-columns: 1fr 3fr;
54 | }
55 | ```
56 |
57 |
58 | ### Explore.
59 | The `.` syntax in twig is a shorthand for some PHP methods and functions. Below is a list of methods Twig will check in the order they will be checked in.
60 |
61 | >```twig
62 | >{{ sandwich.cheese }}
63 | >```
64 |
65 | >```php
66 | >// Array key.
67 | >$sandwich['cheese'];
68 | >// Object property.
69 | >$sandwich->cheese;
70 | >// Also works for magic get (provided you implement magic isset).
71 | >$sandwich->__isset('cheese'); && $sandwich->__get('cheese');
72 | >// Object method.
73 | >$sandwich->cheese();
74 | >// Object get method convention.
75 | >$sandwich->getCheese();
76 | >// Object is method convention.
77 | >$sandwich->isCheese();
78 | >// Method doesn't exist/dynamic method.
79 | >$sandwich->__call('cheese');
80 | >```
81 |
82 | 1. Spend a few moments trying to print out variables and their children.
83 |
84 | ## Questions you may have...
85 | + What if I only want the body text without the surrounding markup?
86 | + Why do you keep telling me to delete my kint statements?
87 |
88 | ## Done ☺
89 | Next stop: [Exercise 9 - Twig Classes](exercise_09-twig-classes.md)
90 |
--------------------------------------------------------------------------------
/exercise_09-twig-classes.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 9:
4 |
5 | Drupal has some handy functions specifically designed for the manipulation of HTML elements.
6 |
7 |
8 | ## Manipulating Classes with Twig
9 |
10 | ### Inspect the `attributes` array.
11 | 1. Add `{{ kint(attributes) }}` in your theme's **node.html.twig**. Review the object and its properties and methods. Note that `addClass()` is an available public method.
12 |
13 | ### Add a class.
14 | 2. Change `` to
15 | ``````. Observe the new value in kint as well as the new class in your site's markup.
16 |
17 |
18 | ### Add multiple classes to the body tag.
19 | 1. Remove all kint() statements from **node.html.twig**.
20 |
21 | 2. Copy classy's **html.html.twig** into your theme.
22 |
23 | ```bash
24 | $ cd MYDRUPAL
25 | $ mkdir themes/custom/acme/templates/html
26 | $ cp core/themes/classy/templates/layout/html.html.twig themes/custom/acme/templates/html/html.html.twig
27 | ```
28 | 3. Clear Cache
29 |
30 | 3. Above the DOCTYPE declaration and below the comments, add the following code.
31 |
32 | ```twig
33 | {% set myclasses = ['red', 'green', 'blue'] %}
34 | ```
35 |
36 | 5. Find the line `` and change it to
37 |
38 | ```twig
39 |
40 | ```
41 |
42 | You should now see classes `red`, `green` and `blue` attached to the body tag.
43 |
44 | ### Create a new custom variable.
45 | Let's create a special body class for the user role.
46 |
47 | 1. Note that `logged_in` is one of the variables available in **html.html.twig** according to its comments.
48 |
49 | 2. Use `{{ kint(user) }}` to inspect the user variable in **html.html.twig**. Search the kint for `account`. Note that the method `getAccount()` is public. If we look back at Exercise 8, we see that we can use `{{ user.account }}` because of that sweet, sweet Twig magic.
50 |
51 | 4. Use `{{ kint(user.account }}`. Search for `roles`. Look at that! Our friends at D.O. also made `getAccount()` public. Now we can do the thing.
52 |
53 | 2. Below ```{% set myclasses = ['red', 'green', 'blue'] %}``` add
54 |
55 | ```twig
56 | {% if logged_in %}
57 | {% set roles = user.account.roles %}
58 | {% endif %}
59 | ```
60 |
61 | 3. Change the body tag to
62 |
63 | ```twig
64 |
65 | ```
66 |
67 | 3. Compare the `` tag for logged in and logged out users. What are the results when you remove the `if` statement?
68 |
69 | -------------------
70 |
71 |
72 | ## Questions you may have...
73 | + How do I remove a class?
74 | + Where can I find other cool twig functions?
75 | + What happened to preprocess functions and the template.php file?
76 | + Why do we copy our template files from the classy theme?
77 | + How did you know the `user` variable was available?
78 | + Why did we do that?
79 |
80 |
81 | ## Done ☺
82 | We're getting pretty good at this!. Let's see what [Exercise 10 - Twig Filters](exercise_10-twig-filters.md) has to offer...
83 |
--------------------------------------------------------------------------------
/exercise_10-twig-filters.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 10:
4 |
5 | ## Advanced Twig
6 |
7 | ### Using filters
8 |
9 | [Twig filters](https://twig.symfony.com/doc/2.x/filters/index.html) allow you to make changes to markup right in the template. Let's use our node title to test some string filters.
10 |
11 | 1. First, we want to get the node title as a string.
12 |
13 | 2. Try `{{ kint(node) }}` in your theme's **node.html.twig** file. Under `Available methods` observe `label()`. It is a public function so we can use it in our twig template.
14 |
15 | 1. Near your other variable declaration in your theme's **node.html.twig** add:
16 |
17 | ```twig
18 | {% set title_string = node.label %}
19 | ```
20 |
21 |
22 | 2. Test it with the following filters.
23 |
24 |
25 | ```twig
26 | clean_class: {{ title_string|clean_class }}
27 | upper: {{ title_string|upper }}
28 | length: {{ title_string|length }}
29 | ```
30 |
31 |
32 | 3. If you have time, try creating arrays, loops and object to test some of the other amazing filters for twig See: https://twig.symfony.com/doc/2.x/filters/index.html.
33 |
34 | ## Questions you may have...
35 | + When should one use Twig filters instead of CSS or PHP?
36 |
37 | ## Done ☺
38 | ¡No Pare! !Sigue! !Sigue! [Exercise 11 - Twig Blocks](exercise_11-twig-block.md)
--------------------------------------------------------------------------------
/exercise_11-twig-block.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 11:
4 |
5 | ## Twig blocks and Drupal blocks
6 |
7 | Twig introduces another "block" concept within Drupal. However, a Twig "block" is not the same as a Drupal block.
8 |
9 | A Twig "block" is an element in the template file that can be overridden, independently from its inherited (parent) template. This means I can reuse the majority of a parent template and just change the one part I need to change. In Drupal 7, you would have to copy the whole template into a new file and be stuck trying to manage two independent templates or use PHP to conditionally include a new file.
10 | Twig "blocks" are used for inheritance and act as placeholders and replacements at the same time.
11 |
12 | [Twig block official documentation](http://twig.sensiolabs.org/doc/tags/extends.html)
13 | http://twig.sensiolabs.org/doc/tags/extends.html
14 |
15 | ### Using twig block inheritence
16 |
17 | In Drupal 8 core, we will use the original example Drupal block, "Powered by Drupal" block, to show you an example of using twig blocks. The "Powered by Drupal" Block utilizes the default block template. This template contains a twig block inside of it named `content`.
18 |
19 | We are going to copy the default block twig template into our theme, rename it and set it to only alter the value of the twig block. This way our new template will follow the default template, but only change the value inside of our Twig block.
20 |
21 | 1. First thing is to make sure that the "Powered by Drupal" block is displayed. If not, go to the block admin page and place the "Powered by Drupal" block into any of your theme's regions.
22 |
23 | 2. **Save** the configuration, and visit a page on the front end of your site. **Make sure you can see the block somewhere on your site**.
24 |
25 | 3. Locate and copy the **block.html.twig** template located at **MYDRUPAL/core/themes/classy/templates/block/block.html.twig** and copy it into your **/templates/block** folder of your custom theme.
26 |
27 | ```bash
28 | $ cd MYDRUPAL
29 | $ mkdir themes/custom/acme/templates/block
30 | $ cp core/themes/classy/templates/block/block.html.twig themes/custom/acme/templates/block/block.html.twig
31 | ```
32 |
33 | 4. Open the new **block.html.twig** file in your preferred code editor. You should see the following code:
34 |
35 | ```twig
36 | {%
37 | set classes = [
38 | 'block',
39 | 'block-' ~ configuration.provider|clean_class,
40 | 'block-' ~ plugin_id|clean_class,
41 | ]
42 | %}
43 |
53 | ```
54 |
55 | 5. We are going to copy and rename this file using information from our debug setup. View the source code on a page where the "Powered by Drupal" block is visible. Locate the code for the block in your browser's dev tools. You should see some additional code that looks like this:
56 |
57 | ```html
58 |
64 | ```
65 |
66 | These are the template suggestions for different components of the page. These come from our theme and twig debug enabling. Drupal is telling you exactly how you can name your new template files to make sure they affect that component. The `x` at the start of a name refers to the template that is currently being used to build that component. Items higher on the list override lower ones.
67 |
68 | 6. So that it only affects the "Powered by Drupal" block, rename the `block.html.twig` file to `block--system-powered-by-block.html.twig`
69 |
70 |
71 | ```bash
72 | $ cd MYDRUPAL
73 | $ mv themes/custom/acme/templates/block/block.html.twig themes/custom/acme/templates/block/block--system-powered-by-block.html.twig
74 | ```
75 |
76 |
77 | 7. Open the `block--system-powered-by-block.html.twig` file and replace all the code in it with the following code:
78 |
79 | ```twig
80 | {% extends '/core/themes/classy/templates/block/block.html.twig' %}
81 |
82 | {% block content %}
83 |
{{ 'I am powered by Drupal 8, haha!'|t }}
84 | {% endblock %}
85 | ```
86 |
87 | 7. Clear cache.
88 |
89 | We only override the content inside the twig block name **"content"**, The rest of the items are still controlled by the default block.html.twig template file. If we had to make a change to all Drupal blocks, like add a wrapping div or a class to all blocks, we could make that change in our default template, and it would still apply to our overridden template.
90 |
91 |
92 | ## Questions you may have...
93 | + What is the `|t` in our twig template?
94 | + What is the `{% trans %}` component in twig?
95 | + Can I use all the Twig functions from SensioLabs?
96 |
97 | ## Done ☺
98 | ... [Exercise 12 - Include SVG](exercise_12-twig-include-svg.md) ...
99 |
--------------------------------------------------------------------------------
/exercise_12-twig-include-svg.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 12:
4 |
5 | ## Include an SVG inline.
6 | Often we want to include SVGs in our project because they load fast and can be easily manipulated using CSS. In this example, we use the Twig `include` function to add svg code to our template inline. This method can be used to include any snippet of HTML that needs to be accessed across templates.
7 |
8 |
9 | 1. Create a folder called **images** in your theme root.
10 |
11 | ```bash
12 | $ cd MYDRUPAL
13 | $ mkdir themes/custom/acme/images
14 | ```
15 |
16 | 2. Copy and paste svg code into a file called **mysvg.svg** inside your newly created **images** folder
17 |
18 | ```bash
19 | $ touch themes/custom/acme/images/mysvg.svg
20 | ```
21 |
22 | You can use an svg on your machine or the example below.
23 |
24 | ```svg
25 |
28 | ```
29 |
30 | 3. Paste the following code into .twig file.
31 |
32 | ```twig
33 | {% include active_theme_path() ~ '/images/mysvg.svg' %}
34 | ```
35 |
36 | 4. View a page that include the template you modified.
37 |
38 |
39 | ## Questions you may have...
40 | + Can other types of files can be included?
41 | + Where does the function `active_theme_path()` come from?
42 |
43 |
44 | ## Done ☺
45 | Onward! [Exercise 13 - Preprocess Functions](exercise_13-preprocess.md)
46 |
--------------------------------------------------------------------------------
/exercise_13-preprocess.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 13:
4 |
5 | ## .theme file and preprocess_page
6 |
7 | Don't make yourself responsible for updating every site on January 1st. Let the computer do the work for you and automatically set items, like the copyright date, to the current year. We will begin to dive into some more advanced elements of Drupal theming including preprocess functions and template variables.
8 |
9 | What is a preprocess function? A preprocess function is a function that allows us to manipulate (add, edit, or remove) our data before our template processes it. Hence, the "pre" in preprocess. It's powered by `hook_preprocess()` function. `hook_preprocess_HOOK()` allows modules to preprocess theme variables, but for a specific theme hook, like node, page, or menu\_link. For our example, we will manipulate the data before it goes to the `page` twig template **page.html.twig**, so we will use
10 | "hook\_preprocess\_**page**()"
11 |
12 | ### Create our .theme file
13 |
14 | 1. Create a file at the root of the Acme theme named **acme.theme** and open in your preferred code editor
15 |
16 | ```bash
17 | $ cd MYDRUPAL
18 | $ touch themes/custom/acme/acme.theme
19 | ```
20 |
21 | 2. Write the following code in your new **acme.theme** file:
22 |
23 | ```php
24 | at the end, but there should be a line of whitespace.**
31 |
32 | 4. Open up **page.html.twig** and near the closing footer element, print out our new variable using the following code:
33 |
34 | ```twig
35 |
36 | {{ copyright }}
37 |
38 | ```
39 | 3. Clear cache and verify your copyright is on the page.
40 |
41 |
42 | ### What year is it?
43 |
44 | Great, it’s printing our variable, but we’re still not much better off than simply adding a block to say the copyright date manually. Let's start adding some logic in **acme.theme**.
45 |
46 | 1. Open **acme.theme** and add some php around our new variable so that it can print the current year instead of our filler message:
47 |
48 | ```php
49 | date('Y'))
53 | );
54 | }
55 | ```
56 | **Note that there is not a closing ?> at the end, but there should be a line of whitespace.**
57 |
58 | ##Questions you may have...
59 | + Where did you find that function name? I would never have guessed that.
60 | + Why don’t I close the PHP tags?
61 | + Why does the new variable have a t() function around it?
62 | + What’s the best way to add markup and styles to our variable?
63 | + Could I have done this on the twig template?
64 |
65 | ##Done ☺
66 | [Exercise 14 - Template Suggestions](exercise_14-new-template-suggestions.md) is ready to bat.
67 |
--------------------------------------------------------------------------------
/exercise_14-new-template-suggestions.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 14:
4 |
5 | ## Create new template suggestions
6 |
7 | In Drupal 7, we used to create new template suggestions in our preprocess functions. In Drupal 8, we use two new functions to create new suggestions for Twig templates.
8 |
9 | These functions are `hook_theme_suggestions_alter()` function and the more targeted, `hook_theme_suggestions_HOOK_alter()` function. The `HOOK` refers to the theme hook being used, like `node` or `page` or `menu_link`. This `HOOK` helps to focus in your suggestions to a specific theme component. In this exercise, we will create new suggestions for our 'node' templates based off if the user is logged in or not.
10 |
11 | 1. Open the **acme.theme** file and add the following function:
12 |
13 | ```php
14 | function acme_theme_suggestions_node_alter(&$suggestions, $variables, $hook) {
15 | // Our code will go here.
16 | }
17 | ```
18 |
19 | 2. Inside of our new function, add the following code:
20 |
21 | ```php
22 | ...
23 | if (\Drupal::currentUser()->isAuthenticated()) {
24 | $bundle = $variables['elements']['#node']->bundle();
25 | $mode = $variables['elements']['#view_mode'];
26 | $view_mode = strtr($mode, '.', '_');
27 |
28 | $suggestions[] = 'node__' . $bundle . '__logged_in';
29 | $suggestions[] = 'node__' . $view_mode . '__logged_in';
30 | }
31 | ...
32 | ```
33 | This will create a new suggestion for content types and node view modes for when the viewer is logged in.
34 |
35 | 3. Clear your caches and visit a node page. View the source code and see if you can find your new template suggestions at the start of your node output.
36 |
37 |
38 | ## Questions you may have...
39 | + What is a theme suggestion HOOK?
40 | + What do I do with these new theme suggestions?
41 |
42 |
43 | ## Done ☺
44 | Yes, there's more. [Exercise 15 - Add Classes with PHP](exercise_15-preprocess-add-classses.md)
45 |
--------------------------------------------------------------------------------
/exercise_15-preprocess-add-classses.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 15:
4 |
5 | ## Adding classes in .theme
6 |
7 | A major job of any theme is to give us control over classes. We use classes to control styling, layouts, and javascript. Drupal and Twig give us many different ways to do this.
8 |
9 | In this exercise, we are going to use `hook_preprocess_html` to gain control over the classes attached to our body tag.
10 |
11 | ### Add classes to our body tag
12 |
13 | 1. Navigate to your theme root, and open your **acme.theme** file.
14 |
15 | 2. Add the following code:
16 |
17 | ```php
18 | function acme_preprocess_html(&$variables) {
19 | // Our code will go here
20 | }
21 | ```
22 |
23 | 3. We want to add our own classes to the body tag. This first one will be for all pages. The second one will set a class for each region that has content. Once completed, save and clear caches. Then view the source code of a page and confirm that our new class is applied to the body tag.
24 |
25 | ```php
26 | // Add 'my-class' to all pages.
27 | function acme_preprocess_html(&$variables) {
28 | $variables['attributes']['class'][] = 'my-excellent-class';
29 | }
30 | ```
31 |
32 | 4. Next, add the following line at the top of your **acme.theme** below `getActiveTheme()->getName();
42 |
43 | $regions = system_region_list($theme);
44 |
45 | foreach ($regions as $region => $region_name) {
46 | $region_class = Html::getClass($region);
47 | if (!empty($variables['page'][$region])) {
48 | $variables['attributes']['class'][] = $region_class . '-active';
49 | }
50 | }
51 | ...
52 | ```
53 |
54 | We are going to use the `Html` PHP Class and its `getClass` method to process our values and make sure they are properly formatted class names. This is the replacement to drupal\_html\_class() and drupal\_clean\_css_identifier(). Because we use this, we have to make sure to declare our dependency on that class. That is why we added the code in part 4. Otherwise, PHP would not know what you are talking about and we would get a delightful PHP fatal error.
55 |
56 |
57 | Go back to your website and refresh. Check out our body tag. Do you see the active region classes? This may be helpful if you want to trigger javascript or CSS in one region based on if there is stuff in another region.
58 |
59 | ## Questions you may have...
60 | + How would I know to use `use Drupal\Component\Utility\Html;`?
61 |
62 |
63 | ## Done ☺
64 | Can you believe it? [Exercise 16 - Form Alter](exercise_16-form-alter.md)
65 |
--------------------------------------------------------------------------------
/exercise_16-form-alter.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 16:
4 |
5 | ## Simple form altering
6 |
7 | A big part of Drupal are forms. For example, content entry forms to add and manage content, search forms to find content, and settings forms to control the numerous parts of our site. Default forms are pretty awesome, but sometimes they just aren't enough. Now and then we need to alter a label, default value, or add libraries for styling or cool javascript functionality.
8 |
9 | Form API and Drupal form alters are still in Drupal 8 and are still incredibly powerful tools to control Drupal. When using a form_alter for theming it is best to keep it to simple alterations such as changing labels, adding/removing classes, editing div structure, and adding HTML5 placeholders like in our following examples. Anything else should really be done in a module form alter.
10 |
11 | **For this exercise, make sure the search block is in a region and visible.**
12 |
13 | ### Simple form altering with `hook_form_alter` function
14 |
15 | 1. Navigate to your theme root
16 |
17 | 2. Locate and open your **acme.theme** file, and add the following code.
18 |
19 | ```php
20 | function acme_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
21 | if ($form_id == 'search_block_form') {
22 | // Add placeholder text
23 | $form['keys']['#attributes']['placeholder'] = t('Search');
24 | }
25 | }
26 | ```
27 | 2. Clear cache, and observe your search block.
28 |
29 | ### Simple form altering with `hook_form_FORM_ID_alter` function
30 |
31 | This function is refined to only affect the form with the matching **FORM_ID** in the function name.
32 |
33 | 1. Replace the above function with the following code:
34 |
35 | ```php
36 | function acme_form_search_block_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
37 | // Add placeholder text
38 | $form['keys']['#attributes']['placeholder'] = t('Search this site.');
39 | }
40 | ```
41 |
42 | ### Adding a javascript via a library to a form
43 | Let's say we want to leverage one of the libraries from core or contrib when our form is visible.
44 |
45 | 1. Search and remove any other instances of kint() in your theme. Make sure the search_kint module is enabled.
46 |
47 | 1. View your site as an anonymous user by opening a new browser or private window. Verify that `form.js` is not included.
48 |
49 | 2. Add the following to the `acme_form_search_block_form_alter` function in **acme.theme**
50 |
51 | ```php
52 | kint(\Drupal::service('library.discovery')->getLibrariesByExtension('core'));
53 | ```
54 |
55 | 3. Search for `form.js` in the the search form provided by kint. You'll see it highlighted under parent `drupal.form`.
56 |
57 | 7. Navigate back to your theme root directory and open your **acme.theme** file.
58 |
59 | 9. Add the following code to your `acme_form_search_block_form_alter` function:
60 |
61 | ```php
62 | // Attach a library from our theme to a form.
63 | $form['#attached']['library'][] = 'core/drupal.form';
64 | ```
65 |
66 | 10. Clear your caches.
67 |
68 | If everything worked, the form.js file will be loaded for whenever the search block is present even for anonymous users.
69 |
70 |
71 |
72 |
73 | ## Questions you may have
74 | * What is the benefit of using **hook\_form\_FORM\_ID\_alter** rather than just **hook\_form\_alter**?
75 | * What else can I do with form alters?
76 | * Can I use `[#attached]` in any function?
77 |
78 | ## Done ☺
79 | You're on fire! Don't stop now! [Exercise 17 - Responsive Images](exercise_17-responsive.md)
80 |
--------------------------------------------------------------------------------
/exercise_17-responsive.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 17:
4 |
5 | ## Breakpoints and setting up responsive images
6 |
7 | Using the core Breakpoint module, you can now define your theme’s breakpoints in code. There is no UI for doing this, but we have a solid API from the Breakpoint module that allows modules and themes to define breakpoints and breakpoint groups, as well as resolution modifiers that come in handy when targeting devices with HD/Retina displays. To keep this exercise a little simpler, we won't worry about modifiers for HD displays.
8 |
9 | Find **toolbar.breakpoints.yml** for and observe how the breakpoints correspond to the administration menu styles.
10 |
11 | **Make sure Breakpoint (breakpoint) and Responsive Image (responsive_image) modules are enabled**
12 |
13 | 1. Visit your modules page, under the "Extend" button in the admin menu.
14 |
15 | 2. Enable the Breakpoint and Responsive Image module if they are not enabled
16 |
17 | ### Create the breakpoints
18 |
19 | 1. Navigate to your theme root directory
20 |
21 | 2. Create a new file called **acme.breakpoints.yml**
22 |
23 | ```bash
24 | $ cd MYDRUPAL
25 | $ touch themes/custom/acme/acme.breakpoints.yml
26 | ```
27 | 3. Open the file in your preferred code editor and add the following.
28 |
29 | ```
30 | acme.mobile:
31 | label: mobile
32 | mediaQuery: '(min-width: 0px)'
33 | weight: 0
34 | multipliers:
35 | - 1x
36 | acme.tablet:
37 | label: tablet
38 | mediaQuery: '(min-width: 1040px)'
39 | weight: 1
40 | multipliers:
41 | - 1x
42 | acme.desktop:
43 | label: desktop
44 | mediaQuery: '(min-width: 1200px)'
45 | weight: 2
46 | multipliers:
47 | - 1x
48 | ```
49 |
50 | 3. Clear cache.
51 |
52 | Together, all of these breakpoints are referred to as a **breakpoint group**.
53 | The weight of these is crucial so that the proper image styles get swapped in depending on viewport size and screen resolution. A breakpoint’s weight should be listed from smallest min-width to largest min-width. Also, note the “multipliers” section. Breakpoint allows for different pixel density multipliers for displaying crisper images on HD/Retina displays: 1x, 1.5x and 2x.
54 |
55 |
56 | ### Create your Responsive image style
57 |
58 | We now need to bring the breakpoint group information and the image styles all together. We do this with a **Responsive image style**.
59 |
60 | 1. Navigate to **/admin/config/media/responsive-image-style** and "Add responsive image style"
61 |
62 | 2. Create the style using the following information:
63 | + Label: `Acme Image`
64 | + Breakpoint group: select `Acme`
65 | + For each breakpoint, choose: `Select a single image style` and set the image style that you want along with each breakpoint.
66 | + 1X DESKTOP [(MIN-WIDTH: 961PX)] => Large (480x480)
67 | + 1X TABLET [(MIN-WIDTH: 601PX)] => Medium (220x220)
68 | + 1X MOBILE [(MIN-WIDTH: 0PX)] => Thumbnail (100x100)
69 | + Fallback image style: Medium (220x220) .
70 |
71 | We now have a Responsive image style, with the breakpoints matched to an image style. Now it is time to apply our Responsive image style to an image field
72 |
73 | ### Configure the display of our image field
74 |
75 | 1. Navigate to the Manage display page for the "Article" content type.
76 |
77 | 2. Change the format of the `Image` field to use `Responsive image`.
78 |
79 | 3. Select `Acme Image` as the Responsive image style we want to use.
80 |
81 | 4. Click **Update**, and then click **Save**
82 |
83 | 5. Clear caches, and then visit an article node with an image.
84 |
85 | If you have responsive testing tools in your browser, use those to emulate different screen sizes. If not just make your window smaller until you can start to see the images changing size.
86 |
87 | ## Done ☺
88 | Almost there! [Exercise 18 - Custom Theme Settings 1](exercise_18-theme-settings1.md)
89 |
--------------------------------------------------------------------------------
/exercise_18-theme-settings1.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 18:
4 |
5 | ## Creating a theme settings file
6 |
7 | Often we need to create small, simple little settings for our theme that can make it more reusable and customizable across sites. We can create new theme settings on our theme's settings page that we can use to help other users configure and customize our theme. For this, we will utilize two additional files. **acme.settings.yml** and **theme-settings.php** file.
8 |
9 | 1. Go to MYDRUPAL.LOCAL/admin/appearance/settings/acme in your browser to see what settings are available by default.
10 |
11 | 2. Create a new file called **acme.settings.yml** in your theme root and open in your preferred code editor.
12 |
13 | ```bash
14 | $ cd MYDRUPAL
15 | $ touch themes/custom/acme/acme.settings.yml
16 | ```
17 |
18 | 3. Add the following code:
19 |
20 | ```
21 | features:
22 | copyright_holder: ''
23 | search_placeholder: 'Search site'
24 | ```
25 |
26 | 5. Create a new file in your theme directory called **theme-settings.php**
27 |
28 | ```bash
29 | $ cd MYDRUPAL
30 | $ touch themes/custom/acme/theme-settings.php
31 | ```
32 |
33 | 6. Add the following code to that file.
34 |
35 | ```php
36 | 'textfield',
42 | '#title' => t('Copyright Holder'),
43 | '#default_value' => theme_get_setting('copyright_holder'),
44 | '#description' => t('This appears in the footer.'),
45 | '#weight' => -10,
46 | ];
47 | $form['color'] = [
48 | '#type' => 'select',
49 | '#title' => t('Color'),
50 | '#options' => [
51 | 'blue' => t('Blue'),
52 | 'green' => t('Green'),
53 | 'yellow' => t('Yellow')
54 | ],
55 | '#default_value' => theme_get_setting('color'),
56 | '#description' => t('Choose a color'),
57 | '#weight' => -10,
58 | ];
59 | }
60 | ```
61 |
62 | 7. Clear cache, then go to our **Appearance** page. Click on **Settings** for your custom theme. You should see two new options for the settings we added.
63 |
64 | 8. Feel free to customize those values. We will use them in the next exercises.
65 |
66 | ## Questions you may have...
67 | + What is `features` in the ***.settings.yml**?
68 | + Where do I find documentation for creating forms?
69 |
70 |
71 | ## Done ☺
72 | One more to go! [Exercise 19 - Custom Theme Settings 2](exercise_19-theme-settings2.md)
73 |
--------------------------------------------------------------------------------
/exercise_19-theme-settings2.md:
--------------------------------------------------------------------------------
1 | #### [Drupal 8 Theming](README.md)
2 |
3 | # Exercise 19:
4 |
5 | ## Apply our theme settings to our custom variables
6 |
7 | Now that we have created some custom theme settings and we can customize them through the UI, let's put them to use. We will capitalize on the functionality we created in an earlier exercise. We will alter our **preprocess_page()** to include the "copyright_holder" variable that will be customizable with one of our new theme settings.
8 |
9 | ### Customize the copyright holder
10 |
11 | 2. In our **acme.theme** file, locate the `acme_preprocess_page()` function.
12 |
13 | 5. Modify the code to include the copyright_holder setting.
14 |
15 | ```php
16 | $variables['copyright'] = t("Copyright @date @holder",
17 | array(
18 | '@date' => date('Y'),
19 | '@holder' => theme_get_setting('copyright_holder')
20 | )
21 | );
22 | ```
23 |
24 | 6. Clear caches and see if it worked. Change the value on the theme settings page if you haven't already (otherwise nothing will really change).
25 |
26 | We have declared our copyright_holder in our theme's settings, and it will override the site name as being the default copyright holder. A simple example, but powerful.
27 |
28 | ## Questions you may have...
29 | + How can I get one of the default theme settings, like the logo?
30 | + What would steps are needed to makek the color switcher on the theme settings page do something?
31 |
32 | ## Done ☺
33 |
34 | Congratulations! You're all done!
--------------------------------------------------------------------------------
/local.services.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | session.storage.options:
3 | # Default ini options for sessions.
4 | #
5 | # Some distributions of Linux (most notably Debian) ship their PHP
6 | # installations with garbage collection (gc) disabled. Since Drupal depends
7 | # on PHP's garbage collection for clearing sessions, ensure that garbage
8 | # collection occurs by using the most common settings.
9 | # @default 1
10 | gc_probability: 1
11 | # @default 100
12 | gc_divisor: 100
13 | #
14 | # Set session lifetime (in seconds), i.e. the time from the user's last
15 | # visit to the active session may be deleted by the session garbage
16 | # collector. When a session is deleted, authenticated users are logged out,
17 | # and the contents of the user's $_SESSION variable is discarded.
18 | # @default 200000
19 | gc_maxlifetime: 200000
20 | #
21 | # Set session cookie lifetime (in seconds), i.e. the time from the session
22 | # is created to the cookie expires, i.e. when the browser is expected to
23 | # discard the cookie. The value 0 means "until the browser is closed".
24 | # @default 2000000
25 | cookie_lifetime: 2000000
26 | #
27 | # Drupal automatically generates a unique session cookie name based on the
28 | # full domain name used to access the site. This mechanism is sufficient
29 | # for most use-cases, including multi-site deployments. However, if it is
30 | # desired that a session can be reused across different subdomains, the
31 | # cookie domain needs to be set to the shared base domain. Doing so assures
32 | # that users remain logged in as they cross between various subdomains.
33 | # To maximize compatibility and normalize the behavior across user agents,
34 | # the cookie domain should start with a dot.
35 | #
36 | # @default none
37 | # cookie_domain: '.example.com'
38 | #
39 | twig.config:
40 | # Twig debugging:
41 | #
42 | # When debugging is enabled:
43 | # - The markup of each Twig template is surrounded by HTML comments that
44 | # contain theming information, such as template file name suggestions.
45 | # - Note that this debugging markup will cause automated tests that directly
46 | # check rendered HTML to fail. When running automated tests, 'debug'
47 | # should be set to FALSE.
48 | # - The dump() function can be used in Twig templates to output information
49 | # about template variables.
50 | # - Twig templates are automatically recompiled whenever the source code
51 | # changes (see auto_reload below).
52 | #
53 | # For more information about debugging Twig templates, see
54 | # https://www.drupal.org/node/1906392.
55 | #
56 | # Not recommended in production environments
57 | # @default false
58 | debug: true
59 | # Twig auto-reload:
60 | #
61 | # Automatically recompile Twig templates whenever the source code changes.
62 | # If you don't provide a value for auto_reload, it will be determined
63 | # based on the value of debug.
64 | #
65 | # Not recommended in production environments
66 | # @default null
67 | auto_reload: null
68 | # Twig cache:
69 | #
70 | # By default, Twig templates will be compiled and stored in the filesystem
71 | # to increase performance. Disabling the Twig cache will recompile the
72 | # templates from source each time they are used. In most cases the
73 | # auto_reload setting above should be enabled rather than disabling the
74 | # Twig cache.
75 | #
76 | # Not recommended in production environments
77 | # @default true
78 | cache: true
79 | renderer.config:
80 | # Renderer required cache contexts:
81 | #
82 | # The Renderer will automatically associate these cache contexts with every
83 | # render array, hence varying every render array by these cache contexts.
84 | #
85 | # @default ['languages:language_interface', 'theme', 'user.permissions']
86 | required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
87 | # Renderer automatic placeholdering conditions:
88 | #
89 | # Drupal allows portions of the page to be automatically deferred when
90 | # rendering to improve cache performance. That is especially helpful for
91 | # cache contexts that vary widely, such as the active user. On some sites
92 | # those may be different, however, such as sites with only a handful of
93 | # users. If you know what the high-cardinality cache contexts are for your
94 | # site, specify those here. If you're not sure, the defaults are fairly safe
95 | # in general.
96 | #
97 | # For more information about rendering optimizations see
98 | # https://www.drupal.org/developing/api/8/render/arrays/cacheability#optimizing
99 | auto_placeholder_conditions:
100 | # Max-age at or below which caching is not considered worthwhile.
101 | #
102 | # Disable by setting to -1.
103 | #
104 | # @default 0
105 | max-age: 0
106 | # Cache contexts with a high cardinality.
107 | #
108 | # Disable by setting to [].
109 | #
110 | # @default ['session', 'user']
111 | contexts: ['session', 'user']
112 | # Tags with a high invalidation frequency.
113 | #
114 | # Disable by setting to [].
115 | #
116 | # @default []
117 | tags: []
118 | # Cacheability debugging:
119 | #
120 | # Responses with cacheability metadata (CacheableResponseInterface instances)
121 | # get X-Drupal-Cache-Tags and X-Drupal-Cache-Contexts headers.
122 | #
123 | # For more information about debugging cacheable responses, see
124 | # https://www.drupal.org/developing/api/8/response/cacheable-response-interface
125 | #
126 | # Not recommended in production environments
127 | # @default false
128 | http.response.debug_cacheability_headers: false
129 | factory.keyvalue:
130 | {}
131 | # Default key/value storage service to use.
132 | # @default keyvalue.database
133 | # default: keyvalue.database
134 | # Collection-specific overrides.
135 | # state: keyvalue.database
136 | factory.keyvalue.expirable:
137 | {}
138 | # Default key/value expirable storage service to use.
139 | # @default keyvalue.database.expirable
140 | # default: keyvalue.database.expirable
141 | # Allowed protocols for URL generation.
142 | filter_protocols:
143 | - http
144 | - https
145 | - ftp
146 | - news
147 | - nntp
148 | - tel
149 | - telnet
150 | - mailto
151 | - irc
152 | - ssh
153 | - sftp
154 | - webcal
155 | - rtsp
156 |
157 | # Configure Cross-Site HTTP requests (CORS).
158 | # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
159 | # for more information about the topic in general.
160 | # Note: By default the configuration is disabled.
161 | cors.config:
162 | enabled: false
163 | # Specify allowed headers, like 'x-allowed-header'.
164 | allowedHeaders: []
165 | # Specify allowed request methods, specify ['*'] to allow all possible ones.
166 | allowedMethods: []
167 | # Configure requests allowed from specific origins.
168 | allowedOrigins: ['*']
169 | # Sets the Access-Control-Expose-Headers header.
170 | exposedHeaders: false
171 | # Sets the Access-Control-Max-Age header.
172 | maxAge: false
173 | # Sets the Access-Control-Allow-Credentials header.
174 | supportsCredentials: false
175 | services:
176 | cache.backend.null:
177 | class: Drupal\Core\Cache\NullBackendFactory
178 |
--------------------------------------------------------------------------------
/settings.local.php:
--------------------------------------------------------------------------------
1 |