Badzilla's Blog http://badzilla.co.uk/blog-home.xml en FreeNAS upgrade to M.2 Solid State Drive Boot Disk http://badzilla.co.uk/freenas-upgrade-m2-solid-state-drive-boot-disk <span>FreeNAS upgrade to M.2 Solid State Drive Boot Disk</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Mon, 01/07/2019 - 18:32</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/20190629_151948-scaled.jpg?itok=XhdOrVpn 325w, /sites/default/files/styles/max_650x650/public/2019-07/20190629_151948-scaled.jpg?itok=PlQdYRb5 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/20190629_151948-scaled.jpg?itok=EB-ibu8s 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/20190629_151948-scaled.jpg?itok=XhdOrVpn" alt="XPG SX6000 PCIe Gen3x2 M.2 2280 Solid State Drive" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/Screenshot%202019-06-18%20at%2007.21.45.png?itok=e81AEVj8 325w, /sites/default/files/styles/max_650x650/public/2019-07/Screenshot%202019-06-18%20at%2007.21.45.png?itok=wOZP7Wt3 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/Screenshot%202019-06-18%20at%2007.21.45.png?itok=PsbkmarR 1300w, /sites/default/files/styles/max_2600x2600/public/2019-07/Screenshot%202019-06-18%20at%2007.21.45.png?itok=kR5gl2zt 1780w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/Screenshot%202019-06-18%20at%2007.21.45.png?itok=e81AEVj8" alt="Device Degraded" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>This blog shows how to upgrade the boot disk on FreeNAS to an M.2 PCIe SSD with a 2280 form factor. I went for an XPG SX6000 256GB drive although other products from different suppliers are readily available. The motivation for the upgrade was born out of necessity. When I upgraded my motherboard in November 2018 and blogged about it here <a href="/building-home-freenas-server-hardware-upgrade">/building-home-freenas-server-hardware-upgrade</a> I bought a board with 4 SATA connections. I elected to use an old 2.5" SATA SSD I got in 2012 as my boot drive, but that used the one spare precious SATA slot after I'd plugged in my three data drives. </p> <p>I noticed I was getting occasional error diagnostics reported by FreeNAS as shown in the screenshot above and repeated here: <em>One or more devices have been removed by the administrator. Sufficient replicas exist in the pool to continue functioning in a degraded state.</em> The message wasn't omnipresent; a reboot tended to clear it for a while but it would return. </p> <p>I decided therefore that it was time to replace the pool disks, but that route was largely closed to me because I didn't have a spare SATA slot. By implementing an M.2 SSD I would free up the SATA slot and be able to replace my drives to 3x6 terabyte drives. The pool drive replacement will be covered in a separate blog.</p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Backup the configuration first</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/Screenshot_2019-06-30_at_09_11_16-edited.png?itok=38UjFdOE 325w, /sites/default/files/styles/max_650x650/public/2019-07/Screenshot_2019-06-30_at_09_11_16-edited.png?itok=1Q2zmNyv 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/Screenshot_2019-06-30_at_09_11_16-edited.png?itok=VK78Ke_v 1300w, /sites/default/files/styles/max_2600x2600/public/2019-07/Screenshot_2019-06-30_at_09_11_16-edited.png?itok=eNMe1E0y 1962w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/Screenshot_2019-06-30_at_09_11_16-edited.png?itok=38UjFdOE" alt="Config Save" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Before doing any work on a FreeNAS it makes eminent sense to take a configuration backup. The beauty of FreeNAS is a clean boot disk can be installed at any time and an old configuration can be restored easily. To take the backup, load up the console, and navigate to System -&gt; General -&gt; Save Config. Since FreeNAS uses a very neat React Bootstrap UI, there is no navigation away from the main url so I can't post a direct URL!</p> <p>Also worth noting - don't save your configuration on pool data drives! I keep mine on my laptop with a copy also on a USB memory stick. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Remove existing boot drive, and create a new USB boot drive</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/20190630_092503-scaled-edited.png?itok=zzWPQCjl 325w, /sites/default/files/styles/max_650x650/public/2019-07/20190630_092503-scaled-edited.png?itok=bLNdvd7y 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/20190630_092503-scaled-edited.png?itok=AicTR9Ui 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/20190630_092503-scaled-edited.png?itok=zzWPQCjl" alt="Spare SATA" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Ok next step is to power off the FreeNAS, and remove the existing boot drive which is my ancient 2.5" SSD drive. This includes unplugging the SATA cable thus providing the sacred slot I will need for my disk capacity upgrade. Above shows my newly liberated slot. Next task is to download the boot image from FreeNAS download site, ensuring that you pick the same version of FreeNAS that is currently being used. If you aren't sure of the version, check out the config file you just saved - it's in the path name. Mine was named <strong>freenas-FreeNAS-11.1-U6 (caffd76fa)-20190630091041.tar.</strong></p> <p>Once you've downloaded the correct version (in my case 11.1-U6) to your laptop or desktop, you will need software to write the iso file to your USB stick. Since I am a Macbook user, I use Etcher, but you may have a personal favourite. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Installing the M.2 SSD card</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/20190630_092447-scaled-edited.png?itok=09JaTNpH 325w, /sites/default/files/styles/max_650x650/public/2019-07/20190630_092447-scaled-edited.png?itok=meunmjL2 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/20190630_092447-scaled-edited.png?itok=qBAso56a 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/20190630_092447-scaled-edited.png?itok=09JaTNpH" alt="M.2 SSD Card Slot" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Whilst the computer is turned off and with the covers off, install the M.2 SSD drive. Locate the slot on the motherboard; mine was situated between the full form PCIe connector and the CPU heatsink. The card slotted in easily, and the motherboard had the screw in place to hold it in position. I was pleased with this since I'd read on line that the screws are a special specification and difficult to come by. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Installation of FreeNAS</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/20190630_093836-scaled.jpg?itok=0SniCWh8 325w, /sites/default/files/styles/max_650x650/public/2019-07/20190630_093836-scaled.jpg?itok=2aVDt66y 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/20190630_093836-scaled.jpg?itok=ISoY-ZNt 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/20190630_093836-scaled.jpg?itok=0SniCWh8" alt="Install / Upgrade" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/20190630_093901-scaled.jpg?itok=ERX6w9N3 325w, /sites/default/files/styles/max_650x650/public/2019-07/20190630_093901-scaled.jpg?itok=molUyoAF 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/20190630_093901-scaled.jpg?itok=_079sWec 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/20190630_093901-scaled.jpg?itok=ERX6w9N3" alt="Discovery of the boot drive" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-07/20190630_094218-edited.jpg?itok=pq-480cG 325w, /sites/default/files/styles/max_650x650/public/2019-07/20190630_094218-edited.jpg?itok=jaTGy6Gn 650w, /sites/default/files/styles/max_1300x1300/public/2019-07/20190630_094218-edited.jpg?itok=5zJVWmO1 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-07/20190630_094218-edited.jpg?itok=pq-480cG" alt="Completed" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Now we've got the M.2 SSD in place, the chassis can be closed. My motherboard has smart booting so I needed no special boot order selection in my BIOS. I simply slotted in the USB stick in an external slot on the case, and powered up. The USB stick instantly became the boot drive and I then went through the following sequence as shown in the screenshots above. </p> <p>1. Click Install/Upgrade</p> <p>2. The possible boot locations are discovered and displayed. It came as a relief that not only my SX6000NP drive was found, it was selected by default! </p> <p>3. FreeNAS is now installed and if everything has gone correctly you should see the completion message above! </p> <p>At this point, reboot the machine and FreeNAS should boot correctly. It won't however contain your configuration since it doesn't know about it. Therefore in my case it'll was assigned an IP address by my DHCP server. I needed that address to point a web browser at it and load the configuration I saved earlier. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Bill of materials</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><table border="1" bordercolor="#FFCC00" cellpadding="3" cellspacing="3" style="background-color:#FFFF99" width="100%"><tbody><tr><td>M.2 Drive</td> <td><a href="https://www.amazon.co.uk/gp/product/B076ZWSW7X/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B076ZWSW7X&amp;linkCode=as2&amp;tag=upcomingfilms-21&amp;linkId=136ad5115770cb0dfdde0df7b79162d5" target="_blank">ADATA ASX6000NP-128GT-C 128GB XPG SX6000 M.2 SSD M.2 2280 PCIe Gen3x2 3D NAND R/W 730/660 MB/s - (Components &gt; SSD Solid State Drive)</a></td> <td>£42.98</td> </tr><tr><td> </td> <td align="RIGHT"><strong>TOTAL</strong></td> <td>£42.98</td> </tr></tbody></table><p>I lucked out here - I only paid £30 since my drive had a slightly damaged box (as you can see in the first screenshot). So a nice £12 saving for zero inconvenience. </p> </div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/hardware" hreflang="en">Hardware</a></span> <span class="field--item"><a href="/freenas" hreflang="en">Freenas</a></span> </span> </div> Mon, 01 Jul 2019 17:32:56 +0000 nigel 168 at http://badzilla.co.uk Drupal 8 as a Static Site: Drupal 8 Contact Form Thank You Page http://badzilla.co.uk/drupal-8-static-site-drupal-8-contact-form-thank-you-page <span>Drupal 8 as a Static Site: Drupal 8 Contact Form Thank You Page</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Sat, 04/05/2019 - 14:50</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Architecture</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Our previous two tutorials on the Drupal 8 as a static site covered the Contact Form and the journey through the action to AWS Lambda to process the form and send an email. Now the final part of the contact form journey - returning back to the static site and showing a Thank You page. </p> <p>In fact in this tutorial the Thank You path will actually be the same path with the contact form on it - so we return to the starting point, but we want to show a message back to the user to thank them for filling out the form. The notification back to the user will have to be JavaScript obviously since we have no backend language functionality.</p> <p>If you remember the redirect back to the static site from AWS uses a HTTP code of 307. This code matches in the incoming HTTP request with the redirect. So since in our case the form was POSTed, then the redirect will also be POSTed back to the static site. This is both a blessing and a curse as we'll see. </p> <p>Common wisdom says that JavaScript can neither detect whether an incoming request is POSTed or GETed, nor retrieve any of the POSTed data. That is theoretically true, but there are always workarounds of course :) :) </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Adding the JavaScript</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">This is very similar to our earlier tutorial on how to add the Elasticsearch JavaScript client. So I won't dwell too much on this. Firstly we need to extend our template_preprocess_page function to add our thank-you JS library. This is the second section of code below, underneath the elasticsearch part<br /><br /><strong>{themename}.theme</strong> - in my case the custom theme is called beezee8. <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">beezee8_preprocess_page</span><span style="color: #007700">(&amp;</span><span style="color: #0000BB">$variables</span><span style="color: #007700">) {<br /><br />    </span><span style="color: #FF8000">// If we are on the search page, load the JS search client api<br />    // and our implementation to Elasticsearch<br />    </span><span style="color: #007700">if (\</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">::</span><span style="color: #0000BB">routeMatch</span><span style="color: #007700">()-&gt;</span><span style="color: #0000BB">getRouteName</span><span style="color: #007700">() == </span><span style="color: #DD0000">'search.view_node_search'</span><span style="color: #007700">) {<br />        </span><span style="color: #0000BB">$variables</span><span style="color: #007700">[</span><span style="color: #DD0000">'#attached'</span><span style="color: #007700">][</span><span style="color: #DD0000">'library'</span><span style="color: #007700">][] = </span><span style="color: #DD0000">'beezee8/elastic-library'</span><span style="color: #007700">;<br />    }<br /><br />    </span><span style="color: #FF8000">// If we are on the contact form page, load the JS to show thank you message<br />    </span><span style="color: #007700">if (\</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">::</span><span style="color: #0000BB">request</span><span style="color: #007700">()-&gt;</span><span style="color: #0000BB">getRequestUri</span><span style="color: #007700">() == </span><span style="color: #DD0000">'/about'</span><span style="color: #007700">) {<br />        </span><span style="color: #0000BB">$variables</span><span style="color: #007700">[</span><span style="color: #DD0000">'#attached'</span><span style="color: #007700">][</span><span style="color: #DD0000">'library'</span><span style="color: #007700">][] = </span><span style="color: #DD0000">'beezee8/thank-you-library'</span><span style="color: #007700">;<br />   }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div> In the code above I get the current path alias and should that equal the '/about' page then I inject my JS library which is defined below.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Libraries file</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The libraries file now needs a further entry for our thank you JS. Again we already created an entry for elasticsearch so I am not going to go over this in too much detail. <br /><br /><strong>{themename}.libraries.yml</strong> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">global-styling: css: theme: css<span class="sy0">/</span>style.css: <span class="br0">{</span><span class="br0">}</span> elastic-library: js: js<span class="sy0">/</span>elasticsearch-js<span class="sy0">/</span>elasticsearch.min.js: <span class="br0">{</span><span class="br0">}</span> js<span class="sy0">/</span>beezee<span class="sy0">/</span>beezee_elastic.js: <span class="br0">{</span><span class="br0">}</span> thank-you-library: js: js<span class="sy0">/</span>beezee<span class="sy0">/</span>beezee_thankyou.js: <span class="br0">{</span><span class="br0">}</span></pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">JavaScript Thank You</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Now here's the JavaScript. I did say that there is no way of knowing whether the HTTP request is GET or POST - well true, but we can deduce it. If we interrogate the referrer and it equals the current URL then we can make the assumption that the form has been POSTed, completed its round trip to AWS Lambda and back with a 307 code, and therefore we should show the thank you message. If you have a look at my code below you will see the conditional, and you will see the markup I inject into the page - it's classic Drupal 8 Bootstrap since that's how my theme is built. Your mileage here will obviously vary dependent upon your own theme.<br /><br /><strong>{themename}_thankyou.js</strong> <div class="geshifilter"><div class="javascript geshifilter-javascript"><pre class="de1"><span class="br0">(</span><span class="kw1">function</span> <span class="br0">(</span>$<span class="sy0">,</span> Drupal<span class="br0">)</span> <span class="br0">{</span>   <span class="kw1">var</span> thank_once<span class="sy0">;</span>   <span class="kw1">function</span> BeezeeThankyou<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">if</span> <span class="br0">(</span><span class="sy0">!</span>thank_once<span class="br0">)</span> <span class="br0">{</span> thank_once <span class="sy0">=</span> <span class="kw2">true</span><span class="sy0">;</span>   <span class="co1">// If we are arriving here from lambda which passes on the referrer from here</span> <span class="co1">// then the form has been POSTed so show the thank you page</span> <span class="kw1">if</span> <span class="br0">(</span>document.<span class="me1">referrer</span> <span class="sy0">==</span> document.<span class="me1">URL</span><span class="br0">)</span> <span class="br0">{</span>   <span class="co1">// Markup to add the thankyou message</span> <span class="kw1">var</span> thanks <span class="sy0">=</span> <span class="br0">[</span> <span class="st0">'&lt;div class="alert alert-success alert-dismissible" role="alert"&gt;'</span><span class="sy0">,</span> <span class="st0">'&lt;button role="button" class="close" data-dismiss="alert" aria-label="Close"&gt;&lt;span aria-hidden="true"&gt;×&lt;/span&gt;&lt;/button&gt;'</span><span class="sy0">,</span> <span class="st0">'&lt;h4 class="sr-only"&gt;Status message&lt;/h4&gt;'</span><span class="sy0">,</span> <span class="st0">'Thank you for contacting me. I will be in touch.'</span><span class="sy0">,</span> <span class="st0">'&lt;/div&gt;'</span> <span class="br0">]</span><span class="sy0">;</span>   <span class="co1">// Inject the markup</span> $<span class="br0">(</span><span class="st0">'.region-highlighted'</span><span class="br0">)</span>.<span class="me1">append</span><span class="br0">(</span>thanks.<span class="me1">join</span><span class="br0">(</span><span class="st0">''</span><span class="br0">)</span><span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span>   Drupal.<span class="me1">behaviors</span>.<span class="me1">beezee_thankyou</span> <span class="sy0">=</span> <span class="br0">{</span> attach<span class="sy0">:</span> <span class="kw1">function</span> <span class="br0">(</span>context<span class="sy0">,</span> settings<span class="br0">)</span> <span class="br0">{</span> BeezeeThankyou<span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="br0">}</span><span class="sy0">;</span> <span class="br0">}</span><span class="br0">)</span><span class="br0">(</span>jQuery<span class="sy0">,</span> Drupal<span class="br0">)</span><span class="sy0">;</span></pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">nginx Server Configuration</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-05/Screenshot%202019-05-04%20at%2014.20.54.png?itok=6tpIS5Ha 325w, /sites/default/files/styles/max_650x650/public/2019-05/Screenshot%202019-05-04%20at%2014.20.54.png?itok=ob3PdIWy 650w, /sites/default/files/styles/max_1300x1300/public/2019-05/Screenshot%202019-05-04%20at%2014.20.54.png?itok=HXKYNHSr 1232w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-05/Screenshot%202019-05-04%20at%2014.20.54.png?itok=6tpIS5Ha" alt="nginx error" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">If we try our form now (or at least if I do since I'm using nginx) - we will get an nginx <strong>405 Not Allowed</strong> error. Remember I said the POST could be a blessing and a curse? The error is because by default nginx will not allow a POST to a static page. Thankfully there is a workaround. Locate the server configuration for your particular virtual host, and edit it by adding the following line into the <strong>server</strong> section. <br /><br /><strong>{VM name}.conf</strong> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">server <span class="br0">{</span> . . . error_page <span class="nu0">405</span> =<span class="nu0">200</span> <span class="re1">$uri</span>; . .</pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Form submission</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-05/Screenshot%202019-05-04%20at%2015.36.57.png?itok=0_8Bbaz8 325w, /sites/default/files/styles/max_650x650/public/2019-05/Screenshot%202019-05-04%20at%2015.36.57.png?itok=k1HGEgk5 650w, /sites/default/files/styles/max_1300x1300/public/2019-05/Screenshot%202019-05-04%20at%2015.36.57.png?itok=InG6NxCW 1300w, /sites/default/files/styles/max_2600x2600/public/2019-05/Screenshot%202019-05-04%20at%2015.36.57.png?itok=xNGfoh3u 2518w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-05/Screenshot%202019-05-04%20at%2015.36.57.png?itok=0_8Bbaz8" alt="form submission" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Ok we should now be good to go. Submit the form and it should do the round trip from static site to AWS Lambda back to static site and an email arriving in your inbox! </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">An even better solution? </div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-05/Screenshot%202019-05-04%20at%2012.22.24.png?itok=gRLJM0fM 325w, /sites/default/files/styles/max_650x650/public/2019-05/Screenshot%202019-05-04%20at%2012.22.24.png?itok=ZCyJqk5L 650w, /sites/default/files/styles/max_1300x1300/public/2019-05/Screenshot%202019-05-04%20at%2012.22.24.png?itok=iJlZV3ZA 1300w, /sites/default/files/styles/max_2600x2600/public/2019-05/Screenshot%202019-05-04%20at%2012.22.24.png?itok=U51-2amo 2494w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-05/Screenshot%202019-05-04%20at%2012.22.24.png?itok=gRLJM0fM" alt="alternative solution" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>If you look carefully above you will see a placeholder in the thank you message. So am I saying it is possible to retrieve the POSTed data and insert the name into the placeholder? Sort of. There is a JS library called <a href="https://github.com/ssut/jQuery-PostCapture">jQuery-PostCapture</a> which can achieve this. It works by capturing the POSTed data when the form is initially submitted. This copy of the data is stored either as a cookie or in the browser's local storage. Once the trip to AWS Lambda is completed, this data can be fetched from the cookie or local storage - so the data isn't fetched from the actual POST itself. </p> <p>The problem I've got with this is neither a cookie nor local storage is secure. However if you feel that the gains (being able to customise a message on the thank you page) is worth the security risk, then give it a shot! </p></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/php" hreflang="en">PHP</a></span> <span class="field--item"><a href="/development" hreflang="en">Development</a></span> <span class="field--item"><a href="/js" hreflang="en">JavaScript</a></span> </span> </div> Sat, 04 May 2019 13:50:36 +0000 nigel 167 at http://badzilla.co.uk Connect Robo 3T to MongoDB via Vagrant Ubuntu VM http://badzilla.co.uk/connect-robo-3t-mongodb-vagrant-ubuntu-vm <span>Connect Robo 3T to MongoDB via Vagrant Ubuntu VM</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Sun, 14/04/2019 - 09:50</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot%202019-04-14%20at%2010.27.55.png?itok=-XAUlNg2 325w, /sites/default/files/styles/max_650x650/public/2019-04/Screenshot%202019-04-14%20at%2010.27.55.png?itok=tVJdLhu4 650w, /sites/default/files/styles/max_1300x1300/public/2019-04/Screenshot%202019-04-14%20at%2010.27.55.png?itok=-tC_lvzU 1300w, /sites/default/files/styles/max_2600x2600/public/2019-04/Screenshot%202019-04-14%20at%2010.27.55.png?itok=bspebmpe 1990w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot%202019-04-14%20at%2010.27.55.png?itok=-XAUlNg2" alt="Robo 3T" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>I use a Mac for development <em>but</em> I don't actually do my development on my Mac. I don't want to pollute my filesystem with a myriad of different programming languages and different versions of those languages. That can lead to instability. I chose to either use Docker containers or VMs within my Mac. This comes with its own problems - sometimes connectivity can be a problem.</p> <p>I am developing a Go web app on a Ubuntu 16.04 VM which is provisioned using Vagrant and an Ansible playbook. The app is using MongoDB as a document store. It's always useful to have a DB GUI to refer to during development. MySQL and Sequel Pro are a match made in heaven. In MongoDB land, the GUI of choice is Robo 3T (Formally Robomongo). Robo 3T is installed on my Mac (Host machine) - now how do I connect it to my Ubuntu VM (Guest machine)?</p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">SSH Tunnel</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot_2019-04-14_at_10_02_46-edited.png?itok=7qpIJ8GP 325w, /sites/default/files/styles/max_650x650/public/2019-04/Screenshot_2019-04-14_at_10_02_46-edited.png?itok=KoLaJgaT 650w, /sites/default/files/styles/max_1300x1300/public/2019-04/Screenshot_2019-04-14_at_10_02_46-edited.png?itok=HWGVSMEL 1300w, /sites/default/files/styles/max_2600x2600/public/2019-04/Screenshot_2019-04-14_at_10_02_46-edited.png?itok=pwbdIix4 2138w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot_2019-04-14_at_10_02_46-edited.png?itok=7qpIJ8GP" alt="Vagrant ssh-config" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>We will be using SSH Tunneling to gain access to the guest machine. Navigate to your project's folder on the host machine in a terminal. This is the directory which contains the Vagrantfile. Issue a <strong>vagrant ssh-config</strong> command. This will give us the information to populate the Robo 3T configuration screens. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Robo 3T Connection - SSH Tab</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot%202019-04-14%20at%2010.12.24.png?itok=18zuGWte 325w, /sites/default/files/styles/max_650x650/public/2019-04/Screenshot%202019-04-14%20at%2010.12.24.png?itok=jXBGQlFW 650w, /sites/default/files/styles/max_1300x1300/public/2019-04/Screenshot%202019-04-14%20at%2010.12.24.png?itok=R2MxSQoN 1300w, /sites/default/files/styles/max_2600x2600/public/2019-04/Screenshot%202019-04-14%20at%2010.12.24.png?itok=17bQv6by 1318w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot%202019-04-14%20at%2010.12.24.png?itok=18zuGWte" alt="SSH Tab" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Open up Robo 3T and navigate to the SSH tab in Connections. Since we are using SSH tunneling, tick the checkbox at the top left of the form. Now you need to populate the rest of the fields. Perhaps counter-intuitively, the SSH address is always your Host, and therefore 127.0.0.1, and as per the Vagrant configuration posted earlier, the port is 2222. The user name is vagrant and we will be using the private key listed above. You'll note that directories starting with the dot file (hidden files) on a Mac aren't shown by default. So to find the .<strong>vagrant </strong>directory, you will need the shift-cmd-dot keystroke combination. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Robo 3T Connection - Connection Tab</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot%202019-04-14%20at%2010.20.48.png?itok=_Y7izpVE 325w, /sites/default/files/styles/max_650x650/public/2019-04/Screenshot%202019-04-14%20at%2010.20.48.png?itok=M4zZIkWM 650w, /sites/default/files/styles/max_1300x1300/public/2019-04/Screenshot%202019-04-14%20at%2010.20.48.png?itok=Kzn6JH2b 1300w, /sites/default/files/styles/max_2600x2600/public/2019-04/Screenshot%202019-04-14%20at%2010.20.48.png?itok=VtKScZAA 1314w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-04/Screenshot%202019-04-14%20at%2010.20.48.png?itok=_Y7izpVE" alt="Connection Tab" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Now go to the connection tab. I need a Direct Connection (and not a replica set) for my VM, and again the address should be your Host's 127.0.0.1. The port however is the default port used by MongoDB - 27017. Once this is done you should be in a position to click Test to check everything is ok. Save the configuration and you should now be able to open the connection to MongoDB and see something to my screen at the top of the page.</p></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/development" hreflang="en">Development</a></span> <span class="field--item"><a href="/go" hreflang="en">Go</a></span> </span> </div> Sun, 14 Apr 2019 08:50:31 +0000 nigel 166 at http://badzilla.co.uk Raspberry Pi Music Box http://badzilla.co.uk/raspberry-pi-music-box <span>Raspberry Pi Music Box</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Wed, 06/02/2019 - 10:45</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Introduction</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/DSC_2199-scaled.JPG?itok=Y_HJMKTb 325w, /sites/default/files/styles/max_650x650/public/2019-02/DSC_2199-scaled.JPG?itok=CMspo0I2 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/DSC_2199-scaled.JPG?itok=tQFCS5wN 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/DSC_2199-scaled.JPG?itok=Y_HJMKTb" alt="Complete solution" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>This bog provides a tutorial for building a Raspberry Pi music box which can be controlled using a hand held device through a web interface. Ideal for a bedroom where there is no desire to have a monitor / keyboard /mouse combination. The cornerstone of the build is the <a href="http://www.runeaudio.com/">Rune Audio distro</a> which supports FLAC, Vorbis and mp3 amongst other formats. I will be using a Raspberry Pi 3 Model B+ along with an IQAudIO Pi-DAC+ daughter board. </p> <p>There is fierce debate between the proponents of Rune Audio, MoOde Audio, and Volumio. I elected for Rune purely because there is a native Android app available, and my smartphone is a Samsung.  </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Bill of Materials</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/20190202_103647-scaled.jpg?itok=LZ6FxaYU 325w, /sites/default/files/styles/max_650x650/public/2019-02/20190202_103647-scaled.jpg?itok=DB6o7TCI 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/20190202_103647-scaled.jpg?itok=K2rutFZL 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/20190202_103647-scaled.jpg?itok=LZ6FxaYU" alt="Raspberry Pi" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/20190202_103757-scaled.jpg?itok=dZIBcP9c 244w, /sites/default/files/styles/max_650x650/public/2019-02/20190202_103757-scaled.jpg?itok=TqIOul8l 488w, /sites/default/files/styles/max_1300x1300/public/2019-02/20190202_103757-scaled.jpg?itok=kGEQ67wX 750w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/20190202_103757-scaled.jpg?itok=dZIBcP9c" alt="DAC" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/20190202_103907-scaled.jpg?itok=s_r0OCUA 244w, /sites/default/files/styles/max_650x650/public/2019-02/20190202_103907-scaled.jpg?itok=uUzKTR5- 488w, /sites/default/files/styles/max_1300x1300/public/2019-02/20190202_103907-scaled.jpg?itok=FbiqcJ5R 750w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/20190202_103907-scaled.jpg?itok=s_r0OCUA" alt="SD Card" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/20190202_104235-scaled.jpg?itok=q_uUdOAE 244w, /sites/default/files/styles/max_650x650/public/2019-02/20190202_104235-scaled.jpg?itok=0ylAVTAG 488w, /sites/default/files/styles/max_1300x1300/public/2019-02/20190202_104235-scaled.jpg?itok=WRlvhl6R 750w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/20190202_104235-scaled.jpg?itok=q_uUdOAE" alt="Power" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/pi-case.jpg?itok=Zs6czw21 325w, /sites/default/files/styles/max_650x650/public/2019-02/pi-case.jpg?itok=o6LCXuJK 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/pi-case.jpg?itok=5oIoASJH 800w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/pi-case.jpg?itok=Zs6czw21" alt="Case" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/pdt-mhl-gigaworks_t40_series_II.jpg?itok=xZyPhSOb 325w, /sites/default/files/styles/max_650x650/public/2019-02/pdt-mhl-gigaworks_t40_series_II.jpg?itok=nywdZ9ky 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/pdt-mhl-gigaworks_t40_series_II.jpg?itok=mC1odJAi 875w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/pdt-mhl-gigaworks_t40_series_II.jpg?itok=xZyPhSOb" alt="Speakers" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><table border="1" bordercolor="#FFCC00" cellpadding="3" cellspacing="3" style="background-color:#FFFF99" width="100%"><tbody><tr><td>Raspberry Pi</td> <td><a href="https://www.amazon.co.uk/gp/product/B07BFH96M3/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B07BFH96M3&amp;linkCode=as2&amp;tag=upcomingfilms-21&amp;linkId=b1d3ac6bdde3349f017cbc69d659d8e3">3BPLUS-R 1.4 GHz 1 GB RAM 64-Bit</a></td> <td>£34.56</td> </tr><tr><td>DAC</td> <td>IQAudIO PI-DAC+</td> <td>£35.00</td> </tr><tr><td>SD Card</td> <td><a href="https://www.amazon.co.uk/gp/product/B07C5MQCYM/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=upcomingfilms-21&amp;creative=6738&amp;linkCode=as2&amp;creativeASIN=B07C5MQCYM&amp;linkId=f675bbec3439457893e9fe718ac2ea66">LXSINO 32GB Micro SD Class 10</a></td> <td>£7.99</td> </tr><tr><td>Power</td> <td><a href="https://www.amazon.co.uk/gp/product/B01CO1ELT8/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B01CO1ELT8&amp;linkCode=as2&amp;tag=upcomingfilms-21&amp;linkId=3573c47a2062b46091ab88fd5ddc571f">Official 5V 2.5A Power Adapter for the Raspberry Pi 3</a></td> <td>£7.99</td> </tr><tr><td>Case</td> <td>IQAudIO PI-CASE+</td> <td>£19.40</td> </tr><tr><td>Speakers</td> <td><a href="https://www.amazon.co.uk/gp/product/B001E5PJ56/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=upcomingfilms-21&amp;creative=6738&amp;linkCode=as2&amp;creativeASIN=B001E5PJ56&amp;linkId=71ad1b8e0e3adb9d9e4c78d600175179">Creative Gigaworks T40 Series II</a></td> <td>£79.99</td> </tr><tr><td> </td> <td align="RIGHT"><strong>TOTAL</strong></td> <td>£184.93</td> </tr></tbody></table><p>Of course I can't guarantee you'll get the same deals I got, but the pricing should at least be indicative. I already had the speakers I bought for a different project a few years ago that never saw the light of day, so that reduced my outlay to around £105 for this project. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Fit and test the DAC.</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/20190206_145604-scaled.jpg?itok=2BfvlsJM 325w, /sites/default/files/styles/max_650x650/public/2019-02/20190206_145604-scaled.jpg?itok=MaWdZ3mU 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/20190206_145604-scaled.jpg?itok=hs0M1aKf 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/20190206_145604-scaled.jpg?itok=2BfvlsJM" alt="Piggy Back" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Fitting the DAC to the Raspberry Pi is quite easy although there are instructions on the <a href="http://www.iqaudio.com/downloads/">IQaudIO</a> site. The DAC comes with some spacers and screws which align with the Pi's own. Since this is a quick sound test and I will be building the components into a proper case at some point, I only tightened the screws finger tight. </p> <p>Once this has been completed, it's time to test it works fine. IQaudIO provide downloadable bootable images that can be written to an SD disk and will automatically play mp3 files. The best file on their downloads page is IQ_0_quicktestDigi*.zip.</p> <p>Unfortunately the zip file was corrupted when I tried this. I reported this to their webmaster. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Build the Pi-CASE+</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/20190210_092212-scaled.jpg?itok=x5hG2iYb 325w, /sites/default/files/styles/max_650x650/public/2019-02/20190210_092212-scaled.jpg?itok=g2x-vYrw 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/20190210_092212-scaled.jpg?itok=vNlRrfkq 1000w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/20190210_092212-scaled.jpg?itok=x5hG2iYb" alt="Pi-CASE+" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>I couldn't find any build instructions for the case, but it is fairly straightforward. The first point to note is there are some standoffs which should be screwed into the four Raspberry Pi screw holes on the corners of the PCB. The case comprises six perspex sides, and they are slotted to ensure there is only one way it can all fit together - so the second point is it is crucial to dry run this first before it is screwed together to ensure there is no attempt to situate any of the panels the wrong way around.</p> <p>The panels fit together very snuggly without any slack and comes with some small plastic feet to prevent it rubbing and sliding on the chosen surface. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Flash the Rune Audio distro</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2017.35.17.png?itok=uMBZ4nLB 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2017.35.17.png?itok=6a-Ercen 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2017.35.17.png?itok=WNU5k7qd 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2017.35.17.png?itok=NnFRVqEq 1588w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2017.35.17.png?itok=uMBZ4nLB" alt="Etcher" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Time to download our chosen repo. This is a small inconvenience because Rune Audio do not have an official Raspberry Pi 3B+ image. However there is a workaround - a community member has created a rar file download that can be fetched from the <a href="http://www.runeaudio.com/forum/rpi-3-b-t6393-10.html">Rune Audio forums</a>.  </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The file is in rar format so on a Mac you will need to install unrar <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ brew <span class="kw2">install</span> unrar ==<span class="sy0">&gt;</span> Pouring unrar-5.7.1.mojave.bottle.tar.gz <span class="sy0">/</span>usr<span class="sy0">/</span>local<span class="sy0">/</span>Cellar<span class="sy0">/</span>unrar<span class="sy0">/</span>5.7.1: <span class="nu0">7</span> files, 520.7KB</pre></div></div> Then we can run the unrar command <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ unrar e RuneAudio_0.4_20180629_SC_Pi3B_Plus_v3_tml3nr.rar   UNRAR <span class="nu0">5.70</span> beta <span class="nu0">1</span> freeware Copyright <span class="br0">(</span>c<span class="br0">)</span> <span class="nu0">1993</span>-<span class="nu0">2019</span> Alexander Roshal     Extracting from RuneAudio_0.4_20180629_SC_Pi3B_Plus_v3_tml3nr.rar   Extracting RuneAudio_0.4_20180629_SC_Pi3B+_v3_tml3nr.img OK All OK</pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>To write the image to the SD card I use Etcher. This is a simple utility app although the command line can be used too with dd - not recommended for those with low experience. Once Etcher has completed it will provide a useful desktop notification. At that point the SD card is ready to be inserted into the Raspberry Pi. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Booting Rune Audio</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot_2019-02-06_at_17_58_33-edited.png?itok=bm80gJOJ 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot_2019-02-06_at_17_58_33-edited.png?itok=N8w5JxVc 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot_2019-02-06_at_17_58_33-edited.png?itok=nVpONhXg 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot_2019-02-06_at_17_58_33-edited.png?itok=bx7fxnQz 2600w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot_2019-02-06_at_17_58_33-edited.png?itok=bm80gJOJ" alt="Boot" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The Pi can be booted once a network cable has been attached. If all goes well, it should be possible to point a web browser at http://runeaudio.local (see screenshot above). We now have the genesis of a music box but it still needs configuring. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">MPD Configuration</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.26.34.png?itok=F_sBi50j 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2019.26.34.png?itok=E8rRGTwm 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2019.26.34.png?itok=Y-A2TzyN 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2019.26.34.png?itok=OG9XYkRA 1462w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.26.34.png?itok=F_sBi50j" alt="MPD" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>There are a number of config screens to work through to get the system functioning correctly. Some of the changes will be specific to my circumstances so your mileage may vary. The MPD (Music Player Daemon) configuration needed one change - the need to select the IQaudIO audio output interface. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Settings configuration</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.30.10.png?itok=uT5_-X_e 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2019.30.10.png?itok=RcP17p3G 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2019.30.10.png?itok=ax6rsT_q 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2019.30.10.png?itok=ocIM9V8K 1402w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.30.10.png?itok=uT5_-X_e" alt="Settings" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>This one in particular may not suit all my readers, but I want to use the Europe/London timezone. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Network settings</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.33.11.png?itok=Io_Rnksz 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2019.33.11.png?itok=ydU38Pta 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2019.33.11.png?itok=-lX_ZUoB 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2019.33.11.png?itok=buqO36XD 1678w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.33.11.png?itok=Io_Rnksz" alt="Network" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.33.58.png?itok=9aL1Nzyh 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2019.33.58.png?itok=VHHh8XWu 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2019.33.58.png?itok=eiokKvW3 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2019.33.58.png?itok=ZzwSSERp 1474w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.33.58.png?itok=9aL1Nzyh" alt="eth0" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>It makes sense to use a static IP address on your home network - so we can use a proxy in order for all devices to find their way to Rune. More of this later, but for now navigate to Network and you should see a sight similar to the first screenshot above. Click on eth0 and you will see the second screenshot. I have populated this form to use 192.168.0.204. My router has already been configured to assign DHCP addresses between 192.168.0.2-&gt;199. Anything 200 and above is reserved for my static IP address home servers. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Sources configuration</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.40.14.png?itok=vh-LJNeC 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2019.40.14.png?itok=AsaIkCY5 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2019.40.14.png?itok=Lsb6fdm_ 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2019.40.14.png?itok=wHwtG758 1674w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2019.40.14.png?itok=vh-LJNeC" alt="Sources" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2020.41.01-edited.png?itok=nPC3bQBF 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2020.41.01-edited.png?itok=QwF8X_dL 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2020.41.01-edited.png?itok=ec-HD2vy 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2020.41.01-edited.png?itok=Ou6QQABN 1484w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2020.41.01-edited.png?itok=nPC3bQBF" alt="Mount" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2020.19.04-edited.png?itok=CxKBa4xm 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2020.19.04-edited.png?itok=WI4z6yG5 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2020.19.04-edited.png?itok=4rT7AtLp 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2020.19.04-edited.png?itok=H7iOZswu 1686w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2020.19.04-edited.png?itok=CxKBa4xm" alt="Updating" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The final configuration activity is to make Rune Audio aware of where the audio files are stored, and in my case they are on an NFS drive on my NAS server. Go to the Sources page as per the first screenshot above and click on <em>Add New Mount</em>. The form to complete for NFS mounts just wouldn't play ball at all, so in desperation I tried SMB/CIFS(OS X Share) and that worked - see the second screenshot. I called my source <em>flac</em> since this share contains all my high fidelity rips. I then repeated the process by creating a mount for <em>mp3</em> for my low definition audio. The third screenshot shows the two mounts listed, and I have instigated a rebuild of the library, and this is shown by the <em>updating</em> spinner bottom left. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Playback</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2020.16.55.png?itok=FpT7Lbk5 325w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot%202019-02-06%20at%2020.16.55.png?itok=RL3-kUoT 650w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot%202019-02-06%20at%2020.16.55.png?itok=N3pSRzjt 1300w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot%202019-02-06%20at%2020.16.55.png?itok=RcsuCtrC 2600w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot%202019-02-06%20at%2020.16.55.png?itok=FpT7Lbk5" alt="Playback" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>As soon as the library has been underway for a short period of time and has added a few artists / albums, playback can be tested. I picked an album at random for my end to end test, and it played seamlessly. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Native Android App - Runeaudio Remote Control</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot_20190206-214520_RuneAudio.jpg?itok=hDkSbqbb 183w, /sites/default/files/styles/max_650x650/public/2019-02/Screenshot_20190206-214520_RuneAudio.jpg?itok=WvHY_5d- 366w, /sites/default/files/styles/max_1300x1300/public/2019-02/Screenshot_20190206-214520_RuneAudio.jpg?itok=_f49HEHl 731w, /sites/default/files/styles/max_2600x2600/public/2019-02/Screenshot_20190206-214520_RuneAudio.jpg?itok=kmh-mEND 1080w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-02/Screenshot_20190206-214520_RuneAudio.jpg?itok=hDkSbqbb" alt="Native App" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The native app scans the local network and automatically shows all the connected RuneAudio players, allowing you to control them from the same place. It also displays the RuneUI in a true full screen, with no browsers bars and weird scroll behaviours.The app uses the avahi protocol to scan the local network. It detects the connected RuneAudio players which expose the avahi service and lists them, showing also their IP and MAC addresses.</p> <p>The resulting UI is actually the same as a web browser but I found it immensely frustrating. My habit is to tap the built-in Samsung back key to navigate and that immediately drops me out of the app. Restarting the app requires the initial network discovery to be run again, and mine steadfastly refused to connect initially, but then suddenly appeared to figure out my network and was rock steady afterwards. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Resolving runeaudio.local - Desktop</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Rune Audio uses the Avahi protocol which theoretically means when added to a local network, pointing a browser to http://runeaudio.local should 'just work'. Whilst setting up Rune Audio I noticed this capability was very hit and miss. There are ways of getting a rock solid configuration - for a desktop the simple solution would be to add runeadio.local to the <em>/etc/hosts</em> file. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><strong>/etc/hosts</strong> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">192.168.0.204 runeaudio.local</pre></div></div> Obviously the IP address should be swapped with whichever you have used.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Resolving runeaudio.local - Handheld Device</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Since /etc/hosts can't be edited on a handheld device such as a tablet or a smartphone, we need another solution. The workaround for this is to use a proxy server as per <a href="/raspberry-pi-squid-proxy-server-testing-hand-held-devices-when-developing-sandbox">my blog where I utilised another Raspberry Pi as a proxy.</a> Firstly log into the proxy server and edit its /etc/hosts like the desktop so you will have an entry <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">192.168.0.204 runeaudio.local</pre></div></div> The Squid proxy will then need a reboot. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co4">$ </span><span class="kw2">sudo</span> service squid3 restart</pre></div></div> You will then have to ensure your handheld device is using the proxy for internet access. Screenshots for achieving this on an iPad and an Android phone are in my other blog just mentioned and are not duplicated here for brevity. Just click though if you need guidance.</div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/hardware" hreflang="en">Hardware</a></span> <span class="field--item"><a href="/raspberrypi" hreflang="en">Raspberry Pi</a></span> </span> </div> Wed, 06 Feb 2019 10:45:32 +0000 nigel 165 at http://badzilla.co.uk Drupal 8 as a Static Site: AWS API Gateway, Lambda and SES Form Processing http://badzilla.co.uk/drupal-8-static-site-aws-api-gateway-lambda-and-ses-form-processing <span>Drupal 8 as a Static Site: AWS API Gateway, Lambda and SES Form Processing</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Wed, 02/01/2019 - 10:38</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Serverless Installation</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The intention is to build our AWS ecosystem using the <a href="https://serverless.com/">serverless</a> product. This will enable us to configure our AWS provisioning in a YML file which is then translated into AWS CloudFormation orchestration. In the same codebase our PHP Lambda function can be built out. It isn't my intention to provide heavy documentation here since the Contact Form architecture was <a href="/drupal-8-static-site-contact-form-architecture">defined in an earlier blog</a>, and I have previously written <a href="/real-world-php-lambda-app">many articles on the serverless / lambda / API Gateway</a> combination of technologies to deliver HTTP content and handle form POSTs. </p> <p>Notwithstanding this, I will of course document points of interest along the way so it all makes sense! </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The first step is to install <strong>serverless</strong> on your system. A dependency is node, so if you don't have that then please follow tutorials elsewhere on the Internet before executing the command below to install globally serverless. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co4">$ </span>npm <span class="kw2">install</span> serverless <span class="re5">-g</span></pre></div></div> We will be using Andy Raines' excellent PHP for AWS Lambda via Serverless Framework repo as our stating point. Issuing the following command will install our PHP serverless project into a directory called d8-contact-form. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ serverless <span class="kw2">install</span> <span class="re5">--url</span> https:<span class="sy0">//</span>github.com<span class="sy0">/</span>araines<span class="sy0">/</span>serverless-php <span class="re5">-n</span> d8-contact-form Serverless: Downloading and installing <span class="st0">"serverless-php"</span>... Serverless: Successfully installed <span class="st0">"d8-contact-form"</span> </pre></div></div> We then cd into the directory and list. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ <span class="kw3">cd</span> d8-contact-form<span class="sy0">/</span> $ <span class="kw2">ls</span> <span class="re5">-las</span> total <span class="nu0">48</span> <span class="nu0">0</span> drwxrwxr-x <span class="nu0">16</span> <span class="nu0">501</span> dialout <span class="nu0">512</span> Jan <span class="nu0">2</span> <span class="nu0">11</span>:<span class="nu0">14</span> . <span class="nu0">0</span> drwxr-xr-x <span class="nu0">14</span> <span class="nu0">501</span> dialout <span class="nu0">448</span> Jan <span class="nu0">2</span> <span class="nu0">11</span>:<span class="nu0">14</span> .. <span class="nu0">4</span> <span class="re5">-rwxr-xr-x</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">660</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> buildphp.sh <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">569</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> CHANGELOG.md <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">430</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> composer.json <span class="nu0">0</span> drwxrwxr-x <span class="nu0">3</span> <span class="nu0">501</span> dialout <span class="nu0">96</span> Jan <span class="nu0">2</span> <span class="nu0">11</span>:<span class="nu0">14</span> config <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">1011</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> dockerfile.buildphp <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">40</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> .gitattributes <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">120</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> .gitignore <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">1330</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> handler.js <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">829</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> handler.php <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">1103</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> LICENSE <span class="nu0">4</span> <span class="re5">-rwxr-xr-x</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">133</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> php <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">3705</span> Feb <span class="nu0">14</span> <span class="nu0">2018</span> README.md <span class="nu0">4</span> <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout <span class="nu0">3008</span> Jan <span class="nu0">2</span> <span class="nu0">11</span>:<span class="nu0">14</span> serverless.yml <span class="nu0">0</span> drwxrwxr-x <span class="nu0">5</span> <span class="nu0">501</span> dialout <span class="nu0">160</span> Jan <span class="nu0">2</span> <span class="nu0">11</span>:<span class="nu0">14</span> src</pre></div></div> Since I don't have Git LFS installed, the PHP executable hasn't been downloaded from the repo correctly. I will download this manually, and add it to the directory. Once completed, we can see it there with: <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ <span class="kw2">ls</span> <span class="re5">-lash</span> <span class="sy0">|</span> <span class="kw2">grep</span> <span class="st0">" php"</span> 27M <span class="re5">-rwxr-xr-x</span> <span class="nu0">1</span> <span class="nu0">501</span> dialout 27M Jan <span class="nu0">2</span> <span class="nu0">11</span>:<span class="nu0">22</span> php</pre></div></div> Now we need to install the PHP dependencies using composer. This does have a dependency on PHP 7.1. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ composer <span class="kw2">install</span> <span class="re5">-o</span> <span class="re5">--no-dev</span> You are running composer with xdebug enabled. This has a major impact on runtime performance. See https:<span class="sy0">//</span>getcomposer.org<span class="sy0">/</span>xdebug Loading composer repositories with package information Updating dependencies Package operations: <span class="nu0">9</span> installs, <span class="nu0">0</span> updates, <span class="nu0">0</span> removals - Installing psr<span class="sy0">/</span>log <span class="br0">(</span>1.1.0<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> - Installing monolog<span class="sy0">/</span>monolog <span class="br0">(</span>1.24.0<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> - Installing symfony<span class="sy0">/</span>polyfill-ctype <span class="br0">(</span>v1.10.0<span class="br0">)</span>: Loading from cache - Installing symfony<span class="sy0">/</span>filesystem <span class="br0">(</span>v4.2.1<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> - Installing symfony<span class="sy0">/</span>config <span class="br0">(</span>v4.2.1<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> - Installing symfony<span class="sy0">/</span>contracts <span class="br0">(</span>v1.0.2<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> - Installing psr<span class="sy0">/</span>container <span class="br0">(</span>1.0.0<span class="br0">)</span>: Loading from cache - Installing symfony<span class="sy0">/</span>dependency-injection <span class="br0">(</span>v4.2.1<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> - Installing symfony<span class="sy0">/</span>yaml <span class="br0">(</span>v4.2.1<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> Writing lock <span class="kw2">file</span> Generating optimized autoload files</pre></div></div> Any Raines' code comes with a very basic Lambda function called <strong>hello</strong>. To prove the environment is working correctly, we should invoke it locally (no need for deploying to AWS yet) and if everything is correct, it'll look like this: <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ serverless invoke <span class="kw3">local</span> <span class="re5">-f</span> hello Got event <span class="br0">[</span><span class="br0">]</span> <span class="br0">[</span><span class="br0">]</span> <span class="br0">{</span> <span class="st0">"statusCode"</span>: <span class="nu0">200</span>, <span class="st0">"body"</span>: <span class="st0">"Go Serverless v1.0! Your function executed successfully!"</span> <span class="br0">}</span></pre></div></div> In an <a href="/running-php-amazon-lambda-serverless">earlier blog</a> I replaced the references of <strong>Hello</strong> with <strong>demo</strong>. I have changed it to <strong>D8ContactForm</strong> for the contact form. By deploying the code to AWS, and submitting the contact form, you will see the same screenshot as shown at the end of the <a href="/drupal-8-static-site-drupal-8-contact-form">previous blog. </a></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">SES Validation of email address</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2012.53.48.png?itok=VklpZIXH 325w, /sites/default/files/styles/max_650x650/public/2019-03/Screenshot%202019-03-24%20at%2012.53.48.png?itok=w6rR8Bo2 650w, /sites/default/files/styles/max_1300x1300/public/2019-03/Screenshot%202019-03-24%20at%2012.53.48.png?itok=Pt0JuZRW 1300w, /sites/default/files/styles/max_2600x2600/public/2019-03/Screenshot%202019-03-24%20at%2012.53.48.png?itok=wYgkoMN1 2600w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2012.53.48.png?itok=VklpZIXH" alt="Email address validation" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2013.03.04-edited.png?itok=gDIdr5NA 325w, /sites/default/files/styles/max_650x650/public/2019-03/Screenshot%202019-03-24%20at%2013.03.04-edited.png?itok=4U2cM8Hw 650w, /sites/default/files/styles/max_1300x1300/public/2019-03/Screenshot%202019-03-24%20at%2013.03.04-edited.png?itok=Y1AhIOSK 1300w, /sites/default/files/styles/max_2600x2600/public/2019-03/Screenshot%202019-03-24%20at%2013.03.04-edited.png?itok=8YS9rsXw 2600w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2013.03.04-edited.png?itok=gDIdr5NA" alt="Verification completed" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>To use the AWS SES service, your email address will need to be verified. This is easy - navigate to Simple Email Service and click on email addresses, then "Verify a new email address". Add the email address and a verification email message will be sent for you to click back and confirm the verification process. Once verified, you'll see the second screenshot above. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Serverless configuration</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The next step is to add the SES configuration to the <strong>serverless.yml</strong> file. Add a custom area for your own variables - in this case the email address. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">custom: SENDER_EMAIL: xxxx<span class="sy0">@</span>xxxxxxxx RECIPIENT_EMAIL: xxxx<span class="sy0">@</span>xxxxxxxx</pre></div></div> And add the SES config to the provider area so it looks like this in its entirety: <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">provider: name: aws runtime: nodejs6.10 stage: dev region: eu-west-<span class="nu0">1</span> environment: SENDER: <span class="co1">${self:custom.SENDER_EMAIL}</span> RECIPIENT: <span class="co1">${self:custom.RECIPIENT_EMAIL}</span> DOMAIN: <span class="st0">"*"</span> iamRoleStatements: - Effect: <span class="st0">"Allow"</span> Action: - <span class="st0">"ses:SendEmail"</span> Resource: <span class="st0">"*"</span></pre></div></div> Then deploy <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ sls deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress...</pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Add AWS SDK to the codebase using composer</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The SDK will make it much easier to build out the SES interface code and can be installed to the codebase in the usual composer way <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ composer require aws<span class="sy0">/</span>aws-sdk-php You are running composer with xdebug enabled. This has a major impact on runtime performance. See https:<span class="sy0">//</span>getcomposer.org<span class="sy0">/</span>xdebug Using version ^<span class="nu0">3.90</span> <span class="kw1">for</span> aws<span class="sy0">/</span>aws-sdk-php .<span class="sy0">/</span>composer.json has been updated</pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Add SDK PHP required libraries</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The AWS PHP SDK requires the <strong>filter and simplexml</strong> libraries that aren't in the PHP executable we deploy to AWS by default. Thankfully there is a way of recompiling the PHP executable and I covered this in <a href="/recompiling-php-serverless-php-and-lambda">an earlier blog.</a> So the tl;dr is <ul><li>Add the filter and simplexml libraries to dockerfile.buildphp</li> <li>Make a note of the php creation date and filesize. </li><li>Make sure buildphp.sh is executable on the command line and run it.</li> <ul></ul></ul></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Add the Serverless and SDK handler code</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Now the AWS SDK is in the code base we can build out the functionality to integrate the SDK. Our first work is to ensure that the recipient and sender email addresses are available to the code. To achieve this, they were defined in the <strong>serverless.yml</strong> in the environment section which can be picked up in <strong>handler.php</strong> and assigned to the <strong>$event</strong> array which is made available to the handler class. Add the following lines before the call to handle <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$event</span><span style="color: #007700">[</span><span style="color: #DD0000">'sender_email'</span><span style="color: #007700">] = </span><span style="color: #0000BB">getenv</span><span style="color: #007700">(</span><span style="color: #DD0000">'SENDER'</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$event</span><span style="color: #007700">[</span><span style="color: #DD0000">'recipient_email'</span><span style="color: #007700">] = </span><span style="color: #0000BB">getenv</span><span style="color: #007700">(</span><span style="color: #DD0000">'RECIPIENT'</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div> Now the handler itself. A few things to note. <ul><li>I have added the necessary <strong>use</strong> clauses to the AWS SDK client and the exception class</li><li>The protected <strong>$client_config</strong> holds the configuration we have - but note that further down I am getting the credentials from the <strong>CredentialProvider</strong></li><li>The recipients are an array and so it is possible if required to add multiples here.</li></ul><br /><strong>D8ContactFormHandler.php</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><br /><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">namespace </span><span style="color: #0000BB">Raines</span><span style="color: #007700">\</span><span style="color: #0000BB">Serverless</span><span style="color: #007700">;<br /><br />require </span><span style="color: #DD0000">'vendor/autoload.php'</span><span style="color: #007700">;<br /><br />use </span><span style="color: #0000BB">Aws</span><span style="color: #007700">\</span><span style="color: #0000BB">Ses</span><span style="color: #007700">\</span><span style="color: #0000BB">SesClient</span><span style="color: #007700">;<br />use </span><span style="color: #0000BB">Aws</span><span style="color: #007700">\</span><span style="color: #0000BB">Exception</span><span style="color: #007700">\</span><span style="color: #0000BB">AwsException</span><span style="color: #007700">;<br /><br />class </span><span style="color: #0000BB">D8ContactFormHandler </span><span style="color: #007700">implements </span><span style="color: #0000BB">Handler<br /></span><span style="color: #007700">{<br />    protected </span><span style="color: #0000BB">$client_config </span><span style="color: #007700">= [<br />        </span><span style="color: #DD0000">'region' </span><span style="color: #007700">=&gt; </span><span style="color: #DD0000">'eu-west-1'</span><span style="color: #007700">,<br />        </span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #DD0000">'2010-12-01'</span><span style="color: #007700">,<br />        </span><span style="color: #DD0000">'credentials.cache' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br />        </span><span style="color: #DD0000">'validation' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">,<br />    ];<br /><br />    </span><span style="color: #FF8000">/**<br />     * {@inheritdoc}<br />     */<br />    </span><span style="color: #007700">public function </span><span style="color: #0000BB">handle</span><span style="color: #007700">(array </span><span style="color: #0000BB">$event</span><span style="color: #007700">, </span><span style="color: #0000BB">Context $context</span><span style="color: #007700">)<br />    {<br />        </span><span style="color: #0000BB">$logger </span><span style="color: #007700">= </span><span style="color: #0000BB">$context</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getLogger</span><span style="color: #007700">();<br />        </span><span style="color: #FF8000">// Comment out for debugging<br />        //$logger-&gt;notice('Got event', $event);<br /><br />        // Set up AWS SDK<br />        </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">client_config</span><span style="color: #007700">[</span><span style="color: #DD0000">'credentials'</span><span style="color: #007700">] = \</span><span style="color: #0000BB">Aws</span><span style="color: #007700">\</span><span style="color: #0000BB">Credentials</span><span style="color: #007700">\</span><span style="color: #0000BB">CredentialProvider</span><span style="color: #007700">::</span><span style="color: #0000BB">env</span><span style="color: #007700">();<br />        </span><span style="color: #0000BB">$SesClient </span><span style="color: #007700">= new </span><span style="color: #0000BB">SesClient</span><span style="color: #007700">(</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">client_config</span><span style="color: #007700">);<br /><br />        </span><span style="color: #0000BB">$sender_email </span><span style="color: #007700">= </span><span style="color: #0000BB">$event</span><span style="color: #007700">[</span><span style="color: #DD0000">'sender_email'</span><span style="color: #007700">];<br />        </span><span style="color: #0000BB">$recipient_emails</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$event</span><span style="color: #007700">[</span><span style="color: #DD0000">'recipient_email'</span><span style="color: #007700">];<br /><br />        return [<br />            </span><span style="color: #DD0000">'statusCode' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">200</span><span style="color: #007700">,<br />            </span><span style="color: #DD0000">'body' </span><span style="color: #007700">=&gt; </span><span style="color: #DD0000">'Go Serverless v1.0! Your function executed successfully!'</span><span style="color: #007700">,<br />        ];<br />    }<br />}<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Email generation</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Now we can insert the code to actually generate the email. All of this is self evident and really doesn't need further commentary from me. It should be inserted just before the return statement. <br /><strong>D8ContactFormHandler.php</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />        </span><span style="color: #FF8000">// Process the submitted form.<br />        // *TODO* This could do with more validation.<br />        </span><span style="color: #0000BB">$fields </span><span style="color: #007700">= [];<br />        </span><span style="color: #0000BB">parse_str</span><span style="color: #007700">(</span><span style="color: #0000BB">$event</span><span style="color: #007700">[</span><span style="color: #DD0000">'body'</span><span style="color: #007700">], </span><span style="color: #0000BB">$fields</span><span style="color: #007700">);<br /><br />        if (!isset(</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'name'</span><span style="color: #007700">])) </span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'name'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'{unknown name}'</span><span style="color: #007700">;<br />        if (!isset(</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'mail'</span><span style="color: #007700">])) </span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'mail'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'{unknown email address}'</span><span style="color: #007700">;<br />        if (!isset(</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'subject'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">])) </span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'subject'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'{unknown subject}'</span><span style="color: #007700">;<br />        if (!isset(</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'message'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">])) </span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'message'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'{unknown message}'</span><span style="color: #007700">;<br /><br />        </span><span style="color: #0000BB">$subject </span><span style="color: #007700">= </span><span style="color: #DD0000">'[badzilla.co.uk website feedback] '</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'subject'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">];<br />        </span><span style="color: #0000BB">$plaintext_body </span><span style="color: #007700">= </span><span style="color: #DD0000">'From: '</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'name'</span><span style="color: #007700">].</span><span style="color: #DD0000">'  '</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'mail'</span><span style="color: #007700">].<br />            </span><span style="color: #DD0000">'Subject: '</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'subject'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">].<br />            </span><span style="color: #DD0000">' Message: '</span><span style="color: #007700">. </span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'message'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">];<br />        </span><span style="color: #0000BB">$html_body </span><span style="color: #007700">=  </span><span style="color: #DD0000">'&lt;h1&gt;'</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'subject'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">].</span><span style="color: #DD0000">'&lt;/h1&gt;'</span><span style="color: #007700">.<br />            </span><span style="color: #DD0000">'&lt;h2&gt;'</span><span style="color: #007700">.</span><span style="color: #DD0000">'From: '</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'name'</span><span style="color: #007700">].</span><span style="color: #DD0000">'  &lt;a href="mailto:"'</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'mail'</span><span style="color: #007700">].</span><span style="color: #DD0000">'"&gt;'</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'mail'</span><span style="color: #007700">].</span><span style="color: #DD0000">'&lt;/a&gt;'</span><span style="color: #007700">.</span><span style="color: #DD0000">'&lt;/h2&gt;'</span><span style="color: #007700">.<br />            </span><span style="color: #DD0000">'&lt;p&gt;'</span><span style="color: #007700">.</span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'message'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">][</span><span style="color: #DD0000">'value'</span><span style="color: #007700">].</span><span style="color: #DD0000">'&lt;/p&gt;'</span><span style="color: #007700">;<br />        </span><span style="color: #0000BB">$char_set </span><span style="color: #007700">= </span><span style="color: #DD0000">'UTF-8'</span><span style="color: #007700">;<br /><br />        try {<br />            </span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">$SesClient</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">sendEmail</span><span style="color: #007700">([<br />                </span><span style="color: #DD0000">'Destination' </span><span style="color: #007700">=&gt; [<br />                    </span><span style="color: #DD0000">'ToAddresses' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$recipient_emails</span><span style="color: #007700">,<br />                ],<br />                </span><span style="color: #DD0000">'ReplyToAddresses' </span><span style="color: #007700">=&gt; [</span><span style="color: #0000BB">$sender_email</span><span style="color: #007700">],<br />                </span><span style="color: #DD0000">'Source' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$sender_email</span><span style="color: #007700">,<br />                </span><span style="color: #DD0000">'Message' </span><span style="color: #007700">=&gt; [<br />                    </span><span style="color: #DD0000">'Body' </span><span style="color: #007700">=&gt; [<br />                        </span><span style="color: #DD0000">'Html' </span><span style="color: #007700">=&gt; [<br />                            </span><span style="color: #DD0000">'Charset' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$char_set</span><span style="color: #007700">,<br />                            </span><span style="color: #DD0000">'Data' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$html_body</span><span style="color: #007700">,<br />                        ],<br />                        </span><span style="color: #DD0000">'Text' </span><span style="color: #007700">=&gt; [<br />                            </span><span style="color: #DD0000">'Charset' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$char_set</span><span style="color: #007700">,<br />                            </span><span style="color: #DD0000">'Data' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$plaintext_body</span><span style="color: #007700">,<br />                        ],<br />                    ],<br />                    </span><span style="color: #DD0000">'Subject' </span><span style="color: #007700">=&gt; [<br />                        </span><span style="color: #DD0000">'Charset' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$char_set</span><span style="color: #007700">,<br />                        </span><span style="color: #DD0000">'Data' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$subject</span><span style="color: #007700">,<br />                    ],<br />                ],<br />            ]);<br />            </span><span style="color: #0000BB">$messageId </span><span style="color: #007700">= </span><span style="color: #0000BB">$result</span><span style="color: #007700">[</span><span style="color: #DD0000">'MessageId'</span><span style="color: #007700">];<br />        } catch (</span><span style="color: #0000BB">AwsException $e</span><span style="color: #007700">) {<br />            </span><span style="color: #FF8000">// output error message if fails<br />            </span><span style="color: #0000BB">$logger</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">notice</span><span style="color: #007700">(</span><span style="color: #DD0000">'Message'</span><span style="color: #007700">, </span><span style="color: #0000BB">$e</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getMessage</span><span style="color: #007700">());<br />            </span><span style="color: #0000BB">$logger</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">notice</span><span style="color: #007700">(</span><span style="color: #DD0000">'AWS Message'</span><span style="color: #007700">, </span><span style="color: #0000BB">$e</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getAwsErrorMessage</span><span style="color: #007700">());<br />        }<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Redirect to thank you page</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Once we've sent the email we want to redirect back to our thank you page. This is done with a 307 code which preserves the original POSTed values so they can be used on the forwarded page. Our use will be to make the message personalised. I have decided that I would like my redirect back to my originating page, but of course you could customise it as you see fit. For my case, I use the Referer header. The code below replaces the existing return functionality.<br /><strong>D8 ContactFormHandler.php</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />        </span><span style="color: #007700">return [<br />            </span><span style="color: #DD0000">'headers' </span><span style="color: #007700">=&gt; [</span><span style="color: #DD0000">'Location' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$event</span><span style="color: #007700">[</span><span style="color: #DD0000">'headers'</span><span style="color: #007700">][</span><span style="color: #DD0000">'Referer'</span><span style="color: #007700">]],<br />            </span><span style="color: #DD0000">'statusCode' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">307</span><span style="color: #007700">,<br />        ];<br />&lt;/</span><span style="color: #0000BB">codE</span><span style="color: #007700">&gt;</span><span style="color: #0000BB">?&gt;</span></span></code></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Debugging</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2018.22.11.png?itok=LTjE38Bn 325w, /sites/default/files/styles/max_650x650/public/2019-03/Screenshot%202019-03-24%20at%2018.22.11.png?itok=uyEGnya5 650w, /sites/default/files/styles/max_1300x1300/public/2019-03/Screenshot%202019-03-24%20at%2018.22.11.png?itok=KbaNuSz5 1300w, /sites/default/files/styles/max_2600x2600/public/2019-03/Screenshot%202019-03-24%20at%2018.22.11.png?itok=BRx1-OBf 2552w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2018.22.11.png?itok=LTjE38Bn" alt="CloudWatch" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>During the development process you will have to do multiple deploys and doubtless you will get numerous error responses from AWS. The best way of working these issues is to use CloudWatch, and a sample screenshot is shown above. The codebase has an excellent logging facility to check variables and these are written out to CloudWatch. Have a look at:</p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$logger</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">notice</span><span style="color: #007700">(</span><span style="color: #DD0000">'Got event'</span><span style="color: #007700">, </span><span style="color: #0000BB">$event</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div> This can be amended for whatever you need to interrogate, but remember the second parameter must be an array.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Git Repository</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The entire codebase discussed here is at <a href="https://github.com/sanddevil/serverless-php-contact-form">https://github.com/sanddevil/serverless-php-contact-form</a> - if you wish to extend its functionality, please send me a pull request. </p></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> <span class="field--item"><a href="/php" hreflang="en">PHP</a></span> <span class="field--item"><a href="/development" hreflang="en">Development</a></span> <span class="field--item"><a href="/serverless" hreflang="en">serverless</a></span> <span class="field--item"><a href="/serverless-php" hreflang="en">serverless-php</a></span> <span class="field--item"><a href="/lambda" hreflang="en">lambda</a></span> </span> </div> Wed, 02 Jan 2019 10:38:46 +0000 nigel 164 at http://badzilla.co.uk Drupal 8 as a Static Site: Drupal 8 Contact Form http://badzilla.co.uk/drupal-8-static-site-drupal-8-contact-form <span>Drupal 8 as a Static Site: Drupal 8 Contact Form</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Mon, 31/12/2018 - 10:38</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Drupal Contact</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2010.41.48.png?itok=g7UCAwiw 325w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot%202018-12-31%20at%2010.41.48.png?itok=fESRp_XM 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot%202018-12-31%20at%2010.41.48.png?itok=2ZEMp22N 1300w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot%202018-12-31%20at%2010.41.48.png?itok=KH1K0fWP 2304w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2010.41.48.png?itok=g7UCAwiw" alt="Extend" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot_2018-12-31_at_11_08_53-edited.png?itok=s4ONl3xB 325w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot_2018-12-31_at_11_08_53-edited.png?itok=Vh-HhXXk 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot_2018-12-31_at_11_08_53-edited.png?itok=tV56yvb4 1300w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot_2018-12-31_at_11_08_53-edited.png?itok=eZdVPGL9 2004w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot_2018-12-31_at_11_08_53-edited.png?itok=s4ONl3xB" alt="Feedback" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2015.03.27.png?itok=pHkxHQJL 325w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot%202018-12-31%20at%2015.03.27.png?itok=f1MqYlsn 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot%202018-12-31%20at%2015.03.27.png?itok=BknQ62jT 1300w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot%202018-12-31%20at%2015.03.27.png?itok=g0ej4cfc 1990w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2015.03.27.png?itok=pHkxHQJL" alt="Display" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Drupal core comes with a Contact form, so the first step is to check it is already enabled by navigating to /admin/modules as per the first screenshot above. </p> <p>Next go to /admin/structure/contact and the default forms are listed including the Website Feedback form which is the one we will be using. See the second screenshot. </p> <p>The third screenshot shows the default fields. I've made two changes here. The form preview requires a Drupal backend so has been removed, and I've disabled the 'Send copy to sender' option since that could encourage spamming. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Add contrib module Contact Block</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Since I want my contact form to be below my 'about' content on my /about route, I need the form to be available as a block. This can be achieved using the contrib module Contact Block and can be installed and enabled in the normal way. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ composer require drupal<span class="sy0">/</span>contact_block Using version ^<span class="nu0">1.4</span> <span class="kw1">for</span> drupal<span class="sy0">/</span>contact_block .<span class="sy0">/</span>composer.json has been updated Gathering patches <span class="kw1">for</span> root package. Loading composer repositories with package information Updating dependencies <span class="br0">(</span>including require-dev<span class="br0">)</span> Package operations: <span class="nu0">1</span> <span class="kw2">install</span>, <span class="nu0">0</span> updates, <span class="nu0">0</span> removals Gathering patches <span class="kw1">for</span> root package. Gathering patches <span class="kw1">for</span> dependencies. This might take a minute. - Installing drupal<span class="sy0">/</span>contact_block <span class="br0">(</span>1.4.0<span class="br0">)</span>: Downloading <span class="br0">(</span><span class="nu0">100</span><span class="sy0">%</span><span class="br0">)</span> <span class="sy0">&gt;</span> Drupal\Core\Composer\Composer::vendorTestCodeCleanup Writing lock <span class="kw2">file</span> Generating autoload files <span class="sy0">&gt;</span> Drupal\Core\Composer\Composer::preAutoloadDump <span class="sy0">&gt;</span> Drupal\Core\Composer\Composer::ensureHtaccess $ drush en contact_block <span class="br0">[</span>success<span class="br0">]</span> Successfully enabled: contact_block</pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Add contact form block to region</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2014.43.19.png?itok=X25q6yyr 325w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot%202018-12-31%20at%2014.43.19.png?itok=EhmZLXPP 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot%202018-12-31%20at%2014.43.19.png?itok=dpgd05YO 1300w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot%202018-12-31%20at%2014.43.19.png?itok=EjGDaXhf 1396w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2014.43.19.png?itok=X25q6yyr" alt="Add block" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2014.44.06.png?itok=cOcHH__W 295w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot%202018-12-31%20at%2014.44.06.png?itok=Fp8pwjJY 590w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot%202018-12-31%20at%2014.44.06.png?itok=JrP_77EW 1180w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot%202018-12-31%20at%2014.44.06.png?itok=jC3cE-Tl 1392w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2014.44.06.png?itok=cOcHH__W" alt="Block settings" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2014.44.27.png?itok=NvZhm9BX 325w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot%202018-12-31%20at%2014.44.27.png?itok=ILVQRXX5 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot%202018-12-31%20at%2014.44.27.png?itok=8RBmyDlo 1300w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot%202018-12-31%20at%2014.44.27.png?itok=_ngrrHLk 1922w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-31%20at%2014.44.27.png?itok=NvZhm9BX" alt="Content Region" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Adding the content form block to a block region should be familiar to most people so I won't go into this in great detail. I have placed it in the content region (first screenshot) at /admin/structure/block, configured it with the title, path restrictions so it only appears on my /about route, and ensured the correct form is to be displayed (second screenshot). The third screenshot shows I have dragged the contact form block to be the final block in the content region.  </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">POST form to external URL</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Since it is not possible in the admin UI to add an external URL for the POST action, we will have to achieve this by a few lines of code. But where are we POSTing the form anyway? This is covered in the next blog, but in a nutshell the URL is autogenerated by AWS API Gateway during the CloudFormation orchestration. I won't go into the details now how that is achieved since it can be read later in this sequence, but here is the url we'll use. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ sls info <span class="sy0">|</span> <span class="kw2">grep</span> POST POST - https:<span class="sy0">//</span>djbbm406h0.execute-api.eu-west-<span class="nu0">2</span>.amazonaws.com<span class="sy0">/</span>dev</pre></div></div> The easiest way of adding that url to the Drupal contact form would be to include it into settings.php and then use a template preprocess function to swap out the 'action' value in the form. <br /><strong>settings.php</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$config</span><span style="color: #007700">[</span><span style="color: #DD0000">'aws:lambda'</span><span style="color: #007700">][</span><span style="color: #DD0000">'url'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'https://djbbm406h0.execute-api.eu-west-2.amazonaws.com/dev'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div> <strong>{themename}.theme</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #FF8000">/**<br /> * @param $variables<br /> */<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">beezee8_preprocess_form</span><span style="color: #007700">(&amp;</span><span style="color: #0000BB">$variables</span><span style="color: #007700">) {<br /><br />  </span><span style="color: #FF8000">// find the contact form and add the lambda contact form action url<br />  </span><span style="color: #007700">if (</span><span style="color: #0000BB">$variables</span><span style="color: #007700">[</span><span style="color: #DD0000">'attributes'</span><span style="color: #007700">][</span><span style="color: #DD0000">'id'</span><span style="color: #007700">] == </span><span style="color: #DD0000">'contact-message-feedback-form'</span><span style="color: #007700">) {<br />    </span><span style="color: #0000BB">$variables</span><span style="color: #007700">[</span><span style="color: #DD0000">'attributes'</span><span style="color: #007700">][</span><span style="color: #DD0000">'action'</span><span style="color: #007700">] = \</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">::</span><span style="color: #0000BB">config</span><span style="color: #007700">(</span><span style="color: #DD0000">'aws:lambda'</span><span style="color: #007700">)-&gt;</span><span style="color: #0000BB">get</span><span style="color: #007700">(</span><span style="color: #DD0000">'url'</span><span style="color: #007700">, </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">);<br />  }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Submission of the contact form</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2011.06.26.png?itok=iSVtBIhS 325w, /sites/default/files/styles/max_650x650/public/2019-03/Screenshot%202019-03-24%20at%2011.06.26.png?itok=WPksDgcB 650w, /sites/default/files/styles/max_1300x1300/public/2019-03/Screenshot%202019-03-24%20at%2011.06.26.png?itok=jAkjc3KA 1300w, /sites/default/files/styles/max_2600x2600/public/2019-03/Screenshot%202019-03-24%20at%2011.06.26.png?itok=-np8CCd4 1384w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2019-03/Screenshot%202019-03-24%20at%2011.06.26.png?itok=iSVtBIhS" alt="Form submission" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Now the form will be submitted to AWS Lambda for onward processing, and ultimately, a redirect back with a thank you message. </p></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> <span class="field--item"><a href="/php" hreflang="en">PHP</a></span> <span class="field--item"><a href="/development" hreflang="en">Development</a></span> </span> </div> Mon, 31 Dec 2018 10:38:02 +0000 nigel 163 at http://badzilla.co.uk Drupal 8 as a Static Site: Contact Form Architecture http://badzilla.co.uk/drupal-8-static-site-contact-form-architecture <span>Drupal 8 as a Static Site: Contact Form Architecture</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Wed, 26/12/2018 - 11:06</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>As I have already mentioned in this sequence of blogs, the intention is to use AWS products for my static site contact form. It's worthwhile explaining the proposed architecture and steps involved to implement such a solution. On the face of it, it may appear somewhat convoluted, but AWS provide a number of complementary products to achieve what we want, and the configuration can be saved in code so it is easy to reproduce. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Architecture</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Form%20Submission%20%281%29.jpg?itok=10FV-4E6 325w, /sites/default/files/styles/max_650x650/public/2018-12/Form%20Submission%20%281%29.jpg?itok=KWSN4VeQ 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Form%20Submission%20%281%29.jpg?itok=bO9_BzQz 960w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Form%20Submission%20%281%29.jpg?itok=10FV-4E6" alt="Architecture Diagram" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The above diagram shows the architecture of our contact form, and the flow through the form ecosystem. Let's have a look at the components. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Webpage with Contact Form</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Drupal comes with a contact form in core, and therefore this is fairly straightforward, albeit with a couple of hurdles to jump. Firstly I would like to place my contact form on my <a href="/about">About Me</a> page underneath my existing bio. To achieve this I need the form to be a block which will require the installation of a contrib module. In addition I want my form's action to POST the form data to an external URL which isn't supported in the core form - so that will need a solution too. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">AWS API Gateway</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The form will be submitted for processing to AWS since we don't have a backend on our own static site - hence why the form needs an external URL for its action parameter. </p> <p>We will use AWS API Gateway which is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. This will give us access to AWS compute backend services to process our POSTed form. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">AWS Lambda Function</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>AWS Lambda is a FaaS (Function as a Service) compute service. It is excellent at running a snippet of code on demand once it's been triggered, for instance in our case by the contact form being POSTed.</p> <p>PHP isn't a supported Lambda language, but there are workarounds for this - as explained in my sequence of blogs <a href="/real-world-php-lambda-app">here</a>. Using these workarounds, we will develop a PHP app to process the form, and in addition we will need the AWS PHP SDK to interface with AWS SES (Simple Email Service). </p> <p>The PHP code will reside in Amazon's S3 object storage, and we will use the <a href="https://serverless.com/">serverless</a> product along with the aws console to manage the deploy process. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">AWS SES</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>AWS SES is a very cost effective method of sending marketing, notification and transactional emails. For the amount of usage a blog contact form would see, it's highly unlikely to go above the AWS free tier threshold. It is the perfect tool for sending the contact form contents onto my personal email account. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Inbox</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The objective is the form contents should arrive into my Gmail inbox for me to read and action if necessary. </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Thank you page</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The last activity of the Lambda function is to redirect the submitted form back to the website's thank you page which will have to be constructed as part of this exercise. The redirect will be a 307 to preserve the POSTed parameters that can be parsed and injected into the thank you message with a few lines of JavaScript.</p></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> <span class="field--item"><a href="/lambda" hreflang="en">lambda</a></span> </span> </div> Wed, 26 Dec 2018 11:06:18 +0000 nigel 162 at http://badzilla.co.uk Drupal 8 as a Static Site: JavaScript Search Client http://badzilla.co.uk/drupal-8-static-site-javascript-search-client <span>Drupal 8 as a Static Site: JavaScript Search Client</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Sat, 15/12/2018 - 10:19</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>We now need to plug the Elasticsearch query that was built in <a href="/drupal-8-static-site-elasticsearch-query">the previous blog</a> into a JavaScript client that has been loaded on the search page as described in another <a href="/drupal-8-static-site-search-architecture-and-drupal-configuration">earlier blog</a>. So a few lines of JavaScript will be required here, and it will need to work on my three environments:</p> <ol><li>Sandbox Drupal site connecting to local Elasticsearch server.</li> <li>Sandbox static site connecting to local Elasticsearch server.</li> <li>Production static site connecting to production Elasticsearch server. </li> </ol><p>The JavaScript will use the elasticsearch.js API library which can be downloaded from <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">here</a>. We need to make our theme aware of our JavaScript files and we only wanted them loading on the node search page route. Our JavaScript file will be called beezee_elastic.js, which is the name of my theme (beezee8) and elastic obviously.  </p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Add the JavaScript to the Beezee theme</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Firstly we need an entry in our theme's info.yml file under the Libraries heading. <br /><strong>beezee8.info.yml</strong><br /><div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">libraries: - <span class="st_h">'beezee8/elastic-library'</span></pre></div></div> Next we need to append to the libraries yml file the location of our JavaScript files - the Elastic API library and our own personal script. <br /><strong>beezee8.libraries.yml</strong> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">elastic-library: js: js<span class="sy0">/</span>elasticsearch-js<span class="sy0">/</span>elasticsearch.min.js: <span class="br0">{</span><span class="br0">}</span> js<span class="sy0">/</span>beezee<span class="sy0">/</span>beezee_elastic.js: <span class="br0">{</span><span class="br0">}</span> </pre></div></div> Obviously we then have to copy our JS files into the filesystem at those locations - for now I will have a placeholder empty file for beezee_elastic.js which we will flesh out later.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Load the JavaScript files on the search page only</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">There are a few ways of doing this, but my favourite is to use a preprocessor function in the {themename}.theme file. We can use hook_preprocess_page for this.<br /><strong>beezee8.theme</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #FF8000">/**<br /> * @param $variables<br /> */<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">beezee8_preprocess_page</span><span style="color: #007700">(&amp;</span><span style="color: #0000BB">$variables</span><span style="color: #007700">) {<br /><br />    </span><span style="color: #FF8000">// If we are on the search page, load the JS search client api<br />    // and our implementation to Elasticsearch<br />    </span><span style="color: #007700">if (\</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">::</span><span style="color: #0000BB">routeMatch</span><span style="color: #007700">()-&gt;</span><span style="color: #0000BB">getRouteName</span><span style="color: #007700">() == </span><span style="color: #DD0000">'search.view_node_search'</span><span style="color: #007700">) {<br />        </span><span style="color: #0000BB">$variables</span><span style="color: #007700">[</span><span style="color: #DD0000">'#attached'</span><span style="color: #007700">][</span><span style="color: #DD0000">'library'</span><span style="color: #007700">][] = </span><span style="color: #DD0000">'beezee8/elastic-library'</span><span style="color: #007700">;<br />    }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">The JavaScript search client</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Ok so finally we are ready to build out our front end search client. Here we go!<br /><strong>beezee_elastic.js</strong> <div class="geshifilter"><div class="javascript geshifilter-javascript"><pre class="de1"><span class="br0">(</span><span class="kw1">function</span> <span class="br0">(</span>$<span class="sy0">,</span> Drupal<span class="br0">)</span> <span class="br0">{</span>   <span class="kw1">var</span> elastic_once<span class="sy0">;</span>   <span class="kw1">function</span> BeezeeElastic<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">if</span> <span class="br0">(</span><span class="sy0">!</span>elastic_once<span class="br0">)</span> <span class="br0">{</span> elastic_once <span class="sy0">=</span> <span class="kw2">true</span><span class="sy0">;</span>   <span class="co1">// Get the params from the url</span> <span class="kw1">var</span> pageURL <span class="sy0">=</span> window.<span class="me1">location</span>.<span class="me1">search</span>.<span class="me1">substring</span><span class="br0">(</span><span class="nu0">1</span><span class="br0">)</span><span class="sy0">;</span> <span class="kw1">var</span> URLVariables <span class="sy0">=</span> pageURL.<span class="me1">split</span><span class="br0">(</span><span class="st0">'&amp;'</span><span class="br0">)</span><span class="sy0">;</span> <span class="kw1">var</span> keys<span class="sy0">;</span> i <span class="sy0">=</span> <span class="nu0">0</span><span class="sy0">;</span> while <span class="br0">(</span>i <span class="sy0">&lt;</span> URLVariables.<span class="me1">length</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">var</span> ParameterName <span class="sy0">=</span> URLVariables<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">split</span><span class="br0">(</span><span class="st0">'='</span><span class="br0">)</span><span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span>ParameterName<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span> <span class="sy0">==</span> <span class="st0">'keys'</span><span class="br0">)</span> <span class="br0">{</span> keys <span class="sy0">=</span> ParameterName<span class="br0">[</span><span class="nu0">1</span><span class="br0">]</span><span class="sy0">;</span> <span class="br0">}</span> i<span class="sy0">++;</span> <span class="br0">}</span>   <span class="co1">// Did we get anything from the URL? If not bail, otherwise do the search.</span> <span class="co1">// We shouldn't get to here so don't show a message to user.</span> <span class="kw1">if</span> <span class="br0">(</span><span class="sy0">!</span>keys<span class="br0">)</span> <span class="br0">{</span> console.<span class="me1">log</span><span class="br0">(</span><span class="st0">'Invalid keys'</span><span class="br0">)</span><span class="sy0">;</span> <span class="kw1">return</span><span class="sy0">;</span> <span class="br0">}</span>   <span class="co1">// Add the search key to the h1 selector</span> $<span class="br0">(</span><span class="st0">"h1"</span><span class="br0">)</span>.<span class="me1">append</span><span class="br0">(</span>Drupal.<span class="me1">t</span><span class="br0">(</span><span class="st0">" for "</span><span class="br0">)</span> <span class="sy0">+</span> keys<span class="br0">)</span><span class="sy0">;</span>   <span class="co1">// Endpoint defined during instantiation of API Client</span> <span class="kw1">var</span> client <span class="sy0">=</span> <span class="kw1">new</span> elasticsearch.<span class="me1">Client</span><span class="br0">(</span><span class="br0">{</span> host<span class="sy0">:</span> <span class="st0">'http://meedjum.test:9200/'</span> <span class="coMULTI">/*,*/</span> <span class="coMULTI">/* log: 'trace' */</span> <span class="br0">}</span><span class="br0">)</span><span class="sy0">;</span>   client.<span class="me1">search</span><span class="br0">(</span><span class="br0">{</span> index<span class="sy0">:</span> <span class="st0">'elasticsearch_index_badzilla_meedjum_staticcontent'</span><span class="sy0">,</span> body<span class="sy0">:</span> <span class="br0">{</span> query<span class="sy0">:</span> <span class="br0">{</span> function_score<span class="sy0">:</span> <span class="br0">{</span> query<span class="sy0">:</span> <span class="br0">{</span> query_string<span class="sy0">:</span> <span class="br0">{</span> query<span class="sy0">:</span> keys<span class="sy0">,</span> fields<span class="sy0">:</span> <span class="br0">[</span> <span class="st0">"title"</span><span class="sy0">,</span> <span class="st0">"*body"</span><span class="sy0">,</span> <span class="st0">"term"</span> <span class="br0">]</span><span class="sy0">,</span> default_operator<span class="sy0">:</span> <span class="st0">"OR"</span> <span class="br0">}</span> <span class="br0">}</span><span class="sy0">,</span> functions<span class="sy0">:</span> <span class="br0">[</span> <span class="br0">{</span> linear<span class="sy0">:</span> <span class="br0">{</span> <span class="st0">"created"</span><span class="sy0">:</span> <span class="br0">{</span> origin<span class="sy0">:</span> <span class="st0">"now"</span><span class="sy0">,</span> offset<span class="sy0">:</span> <span class="st0">"365d"</span><span class="sy0">,</span> scale<span class="sy0">:</span> <span class="st0">"1460d"</span><span class="sy0">,</span> decay<span class="sy0">:</span> <span class="nu0">0.5</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">]</span> <span class="br0">}</span> <span class="br0">}</span><span class="sy0">,</span> highlight<span class="sy0">:</span> <span class="br0">{</span> number_of_fragments<span class="sy0">:</span> <span class="nu0">1</span><span class="sy0">,</span> pre_tags<span class="sy0">:</span> <span class="br0">[</span><span class="st0">"&lt;strong&gt;"</span><span class="br0">]</span><span class="sy0">,</span> post_tags<span class="sy0">:</span> <span class="br0">[</span><span class="st0">"&lt;/strong&gt;"</span><span class="br0">]</span><span class="sy0">,</span> fragment_size<span class="sy0">:</span> <span class="nu0">400</span><span class="sy0">,</span> no_match_size<span class="sy0">:</span> <span class="nu0">400</span><span class="sy0">,</span> phrase_limit<span class="sy0">:</span> <span class="nu0">1</span><span class="sy0">,</span> fields<span class="sy0">:</span> <span class="br0">{</span> <span class="st0">"*body"</span><span class="sy0">:</span> <span class="br0">{</span><span class="br0">}</span><span class="sy0">,</span> <span class="st0">"title"</span><span class="sy0">:</span> <span class="br0">{</span><span class="br0">}</span><span class="sy0">,</span> <span class="st0">"term"</span><span class="sy0">:</span> <span class="br0">{</span><span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span><span class="sy0">,</span> size<span class="sy0">:</span> <span class="nu0">10</span> <span class="br0">}</span> <span class="br0">}</span><span class="sy0">,</span> response<span class="br0">)</span><span class="sy0">;</span>   <span class="coMULTI">/* Process the response from Elastic */</span> <span class="kw1">function</span> response<span class="br0">(</span>err<span class="sy0">,</span> resp<span class="sy0">,</span> status<span class="br0">)</span> <span class="br0">{</span>   <span class="co1">// Are we logged in? If so our jQuery calls will be slightly different due to additional DOM stuff</span> <span class="co1">// for non anonymous</span> <span class="co1">// These are our targets for injecting the content on the page. Markup is Bootstrap 3</span> <span class="kw1">var</span> h1_find<span class="sy0">;</span> <span class="kw1">var</span> h2_find<span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span>$<span class="br0">(</span><span class="st0">".user-logged-in"</span><span class="br0">)</span>.<span class="me1">length</span><span class="br0">)</span> <span class="br0">{</span> h1_find <span class="sy0">=</span> <span class="st0">"h1 + nav:last-child"</span><span class="sy0">;</span> h2_find <span class="sy0">=</span> <span class="st0">"h1 + nav + h2:last-child"</span><span class="sy0">;</span> <span class="br0">}</span> <span class="kw1">else</span> <span class="br0">{</span> h1_find <span class="sy0">=</span> <span class="st0">"h1"</span><span class="sy0">;</span> h2_find <span class="sy0">=</span> <span class="st0">"h1 + h2:last-child"</span><span class="sy0">;</span> <span class="br0">}</span>   <span class="kw1">var</span> hits <span class="sy0">=</span> resp.<span class="me1">hits</span>.<span class="me1">hits</span><span class="sy0">;</span>   <span class="kw1">if</span> <span class="br0">(</span>resp.<span class="me1">hits</span>.<span class="me1">total</span> <span class="sy0">==</span> <span class="nu0">0</span><span class="br0">)</span> <span class="br0">{</span> $<span class="br0">(</span>h1_find<span class="br0">)</span>.<span class="me1">after</span><span class="br0">(</span><span class="st0">"&lt;h3&gt;"</span> <span class="sy0">+</span> Drupal.<span class="me1">t</span><span class="br0">(</span><span class="st0">"Your search yielded no results."</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">"&lt;/h3&gt;"</span><span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="kw1">else</span> <span class="br0">{</span> $<span class="br0">(</span>h1_find<span class="br0">)</span>.<span class="me1">after</span><span class="br0">(</span><span class="st0">"&lt;h2&gt;"</span> <span class="sy0">+</span> Drupal.<span class="me1">t</span><span class="br0">(</span><span class="st0">"Search results"</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">"&lt;/h2&gt;"</span><span class="br0">)</span><span class="sy0">;</span>   <span class="kw1">var</span> inject <span class="sy0">=</span> <span class="st0">"&lt;ol&gt;"</span><span class="sy0">;</span> $.<span class="me1">each</span><span class="br0">(</span>hits<span class="sy0">,</span> <span class="kw1">function</span> <span class="br0">(</span>key<span class="sy0">,</span> value<span class="br0">)</span> <span class="br0">{</span>   <span class="co1">// Url, title and Body</span> <span class="kw1">var</span> prefix <span class="sy0">=</span> <span class="st0">"&lt;li&gt;&lt;h3&gt;"</span><span class="sy0">;</span> <span class="kw1">var</span> suffix <span class="sy0">=</span> <span class="st0">"&lt;/li&gt;"</span><span class="sy0">;</span> <span class="kw1">var</span> title <span class="sy0">=</span> <span class="st0">"&lt;a href=<span class="es0">\"</span>"</span> <span class="sy0">+</span> value._source.<span class="me1">url</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span> <span class="sy0">+</span> <span class="st0">"<span class="es0">\"</span>&gt;"</span> <span class="sy0">+</span> value.<span class="me1">highlight</span>.<span class="me1">title</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span> <span class="sy0">+</span> <span class="st0">"&lt;/a&gt;&lt;/h3&gt;"</span><span class="sy0">;</span>   <span class="co1">// do we have a book body or a paragraph body?</span> <span class="kw1">var</span> body<span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span><span class="kw1">typeof</span> value.<span class="me1">highlight</span>.<span class="me1">blog_body</span> <span class="sy0">!=</span> <span class="st0">'undefined'</span><span class="br0">)</span> <span class="br0">{</span> body <span class="sy0">=</span> value.<span class="me1">highlight</span>.<span class="me1">blog_body</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span><span class="sy0">;</span> <span class="br0">}</span> <span class="kw1">else</span> <span class="br0">{</span> body <span class="sy0">=</span> value.<span class="me1">highlight</span>.<span class="me1">body</span><span class="sy0">;</span> <span class="br0">}</span> body <span class="sy0">=</span> <span class="st0">"&lt;p&gt;"</span> <span class="sy0">+</span> body <span class="sy0">+</span> <span class="st0">"&lt;/p&gt;"</span><span class="sy0">;</span>   inject <span class="sy0">+=</span> prefix <span class="sy0">+</span> title <span class="sy0">+</span> body <span class="sy0">+</span> suffix<span class="sy0">;</span>   <span class="co1">// Add the tag</span> <span class="kw1">if</span> <span class="br0">(</span><span class="kw1">typeof</span> value.<span class="me1">highlight</span>.<span class="me1">term</span> <span class="sy0">!=</span> <span class="st0">'undefined'</span><span class="br0">)</span> <span class="br0">{</span> inject <span class="sy0">+=</span> <span class="st0">"&lt;p&gt;"</span> <span class="sy0">+</span> Drupal.<span class="me1">t</span><span class="br0">(</span><span class="st0">"Tag:"</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">" "</span> <span class="sy0">+</span> value.<span class="me1">highlight</span>.<span class="me1">term</span> <span class="sy0">+</span> <span class="st0">"&lt;/p&gt;"</span><span class="sy0">;</span> <span class="br0">}</span>   <span class="co1">// Author and date</span> <span class="kw1">var</span> gb_obj <span class="sy0">=</span> <span class="kw1">new</span> <span class="kw4">Date</span><span class="br0">(</span>value._source.<span class="me1">created</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span> <span class="sy0">*</span> <span class="nu0">1000</span><span class="br0">)</span><span class="sy0">;</span> <span class="kw1">var</span> gb_date <span class="sy0">=</span> <span class="br0">(</span><span class="st0">'0'</span> <span class="sy0">+</span> gb_obj.<span class="me1">getDate</span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>.<span class="me1">slice</span><span class="br0">(</span><span class="sy0">-</span><span class="nu0">2</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">"/"</span> <span class="sy0">+</span> <span class="br0">(</span><span class="st0">'0'</span> <span class="sy0">+</span> <span class="br0">(</span>gb_obj.<span class="me1">getMonth</span><span class="br0">(</span><span class="br0">)</span> <span class="sy0">+</span> <span class="nu0">1</span><span class="br0">)</span><span class="br0">)</span>.<span class="me1">slice</span><span class="br0">(</span><span class="sy0">-</span><span class="nu0">2</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">"/"</span> <span class="sy0">+</span> gb_obj.<span class="me1">getFullYear</span><span class="br0">(</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">" "</span> <span class="sy0">+</span> <span class="st0">"-"</span> <span class="sy0">+</span> <span class="st0">"&amp;nbsp"</span> <span class="sy0">+</span> gb_obj.<span class="me1">getHours</span><span class="br0">(</span><span class="br0">)</span> <span class="sy0">+</span> <span class="st0">":"</span> <span class="sy0">+</span> gb_obj.<span class="me1">getMinutes</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>   inject <span class="sy0">+=</span> <span class="st0">"&lt;p&gt;"</span> <span class="sy0">+</span> value._source.<span class="me1">author</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span> <span class="sy0">+</span> <span class="st0">" "</span> <span class="sy0">+</span> <span class="st0">"-"</span> <span class="sy0">+</span> <span class="st0">" "</span> <span class="sy0">+</span> gb_date<span class="sy0">;</span>   <span class="br0">}</span><span class="br0">)</span><span class="sy0">;</span> inject <span class="sy0">+=</span> <span class="st0">"&lt;/ol&gt;"</span><span class="sy0">;</span> $<span class="br0">(</span>h2_find<span class="br0">)</span>.<span class="me1">after</span><span class="br0">(</span>inject<span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span>     Drupal.<span class="me1">behaviors</span>.<span class="me1">beezee_elastic</span> <span class="sy0">=</span> <span class="br0">{</span> attach<span class="sy0">:</span> <span class="kw1">function</span> <span class="br0">(</span>context<span class="sy0">,</span> settings<span class="br0">)</span> <span class="br0">{</span> BeezeeElastic<span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="br0">}</span><span class="sy0">;</span> <span class="br0">}</span><span class="br0">)</span><span class="br0">(</span>jQuery<span class="sy0">,</span> Drupal<span class="br0">)</span><span class="sy0">;</span></pre></div></div></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Code commentary</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>I'd like to think the code is self-explanatory, but here are a few words for additional clarification. </p> <p>The first block of code is processing the URL params. We are expecting the <em>?keys={search_phrase}</em>. The search phrase needs to be injected into the Elasticsearch query later. </p> <p>The <em>client</em> object is returned from me instantiating the Elasticsearch API. I needed a parameter of the Elasticsearch endpoint which is currently hardcoded. It will work for my two sandbox environments (Drupal and static) but not for production. I will have to come up with a solution to rewrite the endpoint during the deployment process!</p> <p>The <em>client.search</em> call is our query created earlier, and I have added the search term from the URL. The Elasticsearch API provides two mechanisms for this call - it can return a promise or invoke a callback. I opted for the latter. </p> <p>The <em>response</em> function is the callback and it handles the Elastic response. My first task is to set the selectors where I will inject the results - note that the DOM is different whether I am logged into my sandbox Drupal or not - so my injection anchor points are different dependent upon this circumstance. </p> <p>I add the title, body text, tag, author and published date to my output, concatenating them together, and then inject them into the content area of the screen around ordered list tags. </p> <p>The next result is very similar visually to the default Drupal core search with the addition of my blog tags, although of course it can be styled differently if wanted. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">The results</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-15%20at%2015.04.52.png?itok=ZnDNHXFC 325w, /sites/default/files/styles/max_650x650/public/2018-12/Screenshot%202018-12-15%20at%2015.04.52.png?itok=w9IqdciG 650w, /sites/default/files/styles/max_1300x1300/public/2018-12/Screenshot%202018-12-15%20at%2015.04.52.png?itok=WNHqHJYw 1300w, /sites/default/files/styles/max_2600x2600/public/2018-12/Screenshot%202018-12-15%20at%2015.04.52.png?itok=XruPF1MZ 1766w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-12/Screenshot%202018-12-15%20at%2015.04.52.png?itok=ZnDNHXFC" alt="results" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The script works well on both the Drupal and static sites in sandbox. The screenshot above shows a search on the word <em>nginx</em>. At the moment I haven't built in a paginator, but that could come along later. </p></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> <span class="field--item"><a href="/js" hreflang="en">JavaScript</a></span> </span> </div> Sat, 15 Dec 2018 10:19:04 +0000 nigel 161 at http://badzilla.co.uk Drupal 8 as a Static Site: Elasticsearch Query http://badzilla.co.uk/drupal-8-static-site-elasticsearch-query <span>Drupal 8 as a Static Site: Elasticsearch Query</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Wed, 28/11/2018 - 14:35</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>The most fundamental part of the search is the query - this will determine which documents will be retrieved after a search. It must obviously reflect the search phrase input into the search form, and the results returned should be presented in an order of most relevant first. </p> <p>Elasticsearch is excellent at this - but it's crucial that the query reflects exactly what I want to achieve and it searches the correct fields. No two sites are the same and therefore no two search requirements are identical. I have decided that I want to search on the following:</p> <ol><li>Blog title</li> <li>Blog body text (a custom defined Paragraph type)</li> <li>Body text (only used with the book content type and not my regular blogs)</li> <li>Technology vocabulary which I use to tag my blog</li> </ol><p>Each of these will be assigned a score during the search process and the results delivered back in high to low score order.</p></div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Initial basic query</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Unless you re an expert in Elastsearch queries (I am definitely not that), you will need a useful tool with automatic syntax suggestions, and lo and behold Kibana is excellent at this. My first stab at the query is shown below, and it behaved pretty much to my expectations <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">GET <span class="sy0">/</span>myindex<span class="sy0">/</span>_search <span class="br0">{</span> <span class="st0">"query"</span>: <span class="br0">{</span> <span class="st0">"query_string"</span>: <span class="br0">{</span> <span class="st0">"query"</span>: <span class="st0">"Search phrase here"</span>, <span class="st0">"fields"</span>: <span class="br0">[</span> <span class="st0">"title"</span>, <span class="st0">"*body"</span>, <span class="st0">"term"</span> <span class="br0">]</span>, <span class="st0">"default_operator"</span>: <span class="st0">"or"</span> <span class="br0">}</span> <span class="br0">}</span>, <span class="st0">"highlight"</span>: <span class="br0">{</span> <span class="st0">"number_of_fragments"</span> : <span class="nu0">1</span>, <span class="st0">"pre_tags"</span>: <span class="br0">[</span> <span class="st0">"&lt;strong&gt;"</span> <span class="br0">]</span>, <span class="st0">"post_tags"</span>: <span class="br0">[</span> <span class="st0">"&lt;/strong&gt;"</span> <span class="br0">]</span>, <span class="st0">"fragment_size"</span>: <span class="nu0">400</span>, <span class="st0">"no_match_size"</span>: <span class="nu0">400</span>, <span class="st0">"phrase_limit"</span>: <span class="nu0">1</span>, <span class="st0">"fields"</span> : <span class="br0">{</span> <span class="st0">"*body"</span>: <span class="br0">{</span><span class="br0">}</span>, <span class="st0">"title"</span>: <span class="br0">{</span><span class="br0">}</span>, <span class="st0">"term"</span>: <span class="br0">{</span><span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span>, <span class="st0">"size"</span>: <span class="nu0">10</span> <span class="br0">}</span></pre></div></div> Some of this is self-explanatory, some less so. Let's cherry pick our explanations.<br /><strong>*body</strong> - I have two fields which contain the word body and these can be searched using a wildcard.<br /><strong>highlight</strong> - This is where I specify what I actually want to display back to the end user. The following parameters defined this. <br /><strong>number_of_fragments</strong> - The maximum number of fragments to return.<br /><strong>pre_tags</strong> and <strong>post_tags</strong> - When the search term is found, we can specify what tags we want before and after. I chose <strong>strong</strong> instead of the default <strong>em</strong><br /><strong>fragment_size</strong> - How many characters the snipped of the find will contain as a maximum, although it could be less depending upon how Elasticsearch's paragraph and line break algorithm interprets the result<br /><strong>no_match_size</strong> - As above if there are no matches in a particular field. I still want to show the snippet so the user has got some context.<br /><strong>phrase_limit</strong> - Controls the number of matching phrases in a document that are considered.<br /><br /> The major shortcoming of this query is it shows no favouritism towards the date the blog is published. Whether it was published yesterday or ten years ago, the score will be same.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Adding a date decay to favour more recent blogs</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-11/DateDecay.png?itok=hlw5y4c6 325w, /sites/default/files/styles/max_650x650/public/2018-11/DateDecay.png?itok=g_CEfRV0 650w, /sites/default/files/styles/max_1300x1300/public/2018-11/DateDecay.png?itok=9dtA-6wg 813w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-11/DateDecay.png?itok=hlw5y4c6" alt="Elasticsearch date decay" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Technical blogs are ephemeral and transitory. They tend to have a shelf life when they are very popular, but then their popularity will wane as time goes by. This can be due to a number of factors - such as the popularity of the subject of the blog, the technology written about evolving thus obsoleting the origin blog, and the subject of the blog becoming commonplace or covered more extensively in official documentation.</p> <p>Therefore we need a mechanism to favour newer blogs in our search that will be more relevant to our users. Elasticsearch has the <strong>decay</strong> feature, and it supports three different mathematical formula. There is <em>gauss</em>, <em>linear</em> and <em>exp</em>. I had a think here - I could use the default Gaussian approach and with some subtle parameter settings I think it would have done a good job. I could also have used Exponential, with the relatively shallow curve to present the passing of time. In the end I decided I liked a linear representation. </p> <p>My rationale is this: A blog will be totally relevant for about a year, then it will decay at a linear rate for a number of years, then it will hit the bottom. Sheer speculation drove me to use a total decay period of eight years although I concede this is somewhat arbitrary and probably sub-optimal. </p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><br />The syntax for representing this is a little arcane. Here we go: <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="st0">"function_score"</span>: <span class="br0">{</span> <span class="st0">"functions"</span>: <span class="br0">[</span> <span class="br0">{</span> <span class="st0">"linear"</span>: <span class="br0">{</span> <span class="st0">"created"</span>: <span class="br0">{</span> <span class="st0">"origin"</span>: <span class="st0">"now"</span>, <span class="st0">"offset"</span>: <span class="st0">"365d"</span>, <span class="st0">"scale"</span>: <span class="st0">"1460d"</span>, <span class="st0">"decay"</span>: <span class="nu0">0.5</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">]</span> <span class="br0">}</span></pre></div></div> Here is the commentary:<br /><strong>created</strong> - This is the name of the date field for the date the content was published<br /><strong>origin</strong> - The start point for our time span, which will always be anchored at the current date.<br /><strong>offset</strong> - This is the initial flat spot at the top of the graph above. I am saying here - all documents in the first year will be scored equally from a date created perspective.<br /><strong>scale</strong> - Defines the distance from origin + offset at which the computed score will equal decay parameter. <br /><strong>decay</strong> - The decay parameter defines how documents are scored at the distance given at scale. So if you divide decay into scale you get the figure of 8 years.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">The complete query</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Adding the decay to the initial query means that they <strong>multiplied together</strong> (inferred by default). <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">GET <span class="sy0">/</span>myindex<span class="sy0">/</span>_search <span class="br0">{</span>   <span class="st0">"query"</span>: <span class="br0">{</span> <span class="st0">"function_score"</span>: <span class="br0">{</span> <span class="st0">"query"</span>: <span class="br0">{</span> <span class="st0">"query_string"</span>: <span class="br0">{</span> <span class="st0">"query"</span>: <span class="st0">"Search phrase here"</span>, <span class="st0">"fields"</span>: <span class="br0">[</span> <span class="st0">"title"</span>, <span class="st0">"*body"</span>, <span class="st0">"term"</span> <span class="br0">]</span>, <span class="st0">"default_operator"</span>: <span class="st0">"OR"</span> <span class="br0">}</span> <span class="br0">}</span>, <span class="st0">"functions"</span>: <span class="br0">[</span> <span class="br0">{</span> <span class="st0">"linear"</span>: <span class="br0">{</span> <span class="st0">"created"</span>: <span class="br0">{</span> <span class="st0">"origin"</span>: <span class="st0">"now"</span>, <span class="st0">"offset"</span>: <span class="st0">"365d"</span>, <span class="st0">"scale"</span>: <span class="st0">"1460d"</span>, <span class="st0">"decay"</span>: <span class="nu0">0.5</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span> <span class="br0">]</span> <span class="br0">}</span> <span class="br0">}</span>, <span class="st0">"highlight"</span>: <span class="br0">{</span> <span class="st0">"number_of_fragments"</span> : <span class="nu0">1</span>, <span class="st0">"pre_tags"</span>: <span class="br0">[</span> <span class="st0">"&lt;strong&gt;"</span> <span class="br0">]</span>, <span class="st0">"post_tags"</span>: <span class="br0">[</span> <span class="st0">"&lt;/strong&gt;"</span> <span class="br0">]</span>, <span class="st0">"fragment_size"</span>: <span class="nu0">400</span>, <span class="st0">"no_match_size"</span>: <span class="nu0">400</span>, <span class="st0">"phrase_limit"</span>: <span class="nu0">1</span>, <span class="st0">"fields"</span> : <span class="br0">{</span> <span class="st0">"*body"</span>: <span class="br0">{</span><span class="br0">}</span>, <span class="st0">"title"</span>: <span class="br0">{</span><span class="br0">}</span>, <span class="st0">"term"</span>: <span class="br0">{</span><span class="br0">}</span> <span class="br0">}</span> <span class="br0">}</span>, <span class="st0">"size"</span>: <span class="nu0">10</span> <span class="br0">}</span></pre></div></div></div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> </span> </div> Wed, 28 Nov 2018 14:35:57 +0000 nigel 160 at http://badzilla.co.uk Drupal 8 as a Static Site: Disable Core Search http://badzilla.co.uk/drupal-8-static-site-disable-core-search <span>Drupal 8 as a Static Site: Disable Core Search</span> <span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">nigel</span></span> <span>Sun, 18/11/2018 - 10:04</span> <div class="field field--name-field-heading-image-text field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item"><p>Since the static site will not have access to the Drupal 8 Core Search facility, it can be removed by using the command <em>drush pm_uninstall search</em> on the command line, or by visiting <em>admin/modules/uninstall.</em></p> <p>The problem with that approach is core search also has the search form that appears as a block in the navbar on every page of my website. If I uninstall core search I lose the form at the same time. It is my desire to keep on using the search form despite having an Elasticsearch backend. The proposed Elasticsearch JavaScript client will be installed on the /search route which is where the core search form action is directed to. </p> <p>Okay, so feasibly I could uninstall the core search and do either:</p> <ol><li>Create a custom block with a search form that replicates (from an HTML frontend perspective) the core search form.</li> <li>Copy the generated markup from the core search form, and paste it into the page template.</li> </ol><p>I don't like either approach. I would prefer to keep the core search and stub it so it doesn't actually do any backend searching. That way I will have near identical functionality for both my sandbox static and sandbox Drupal sites. </p> <p>To stub the search I would have to alter the default search route and replace it with my own custom route with a controller that returns nothing.</p> </div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Define the route subscriber config</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">The first step is to add to our custom module's services file.<br /><strong>badzilla_static.services.yml</strong> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"> <span class="sy0">&lt;</span>-- previous entry snipped --<span class="sy0">&gt;</span> badzilla.route_subscriber: class: Drupal\badzilla_static\Routing\RouteSubscriber tags: - <span class="br0">{</span> name: event_subscriber <span class="br0">}</span></pre></div></div> We have now defined where our route subscriber lives. Let's create.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Route subscriber class</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">We need to create the directory structure and the file itself first. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ <span class="kw2">mkdir</span> src<span class="sy0">/</span>Routing $ <span class="kw2">touch</span> src<span class="sy0">/</span>Routing<span class="sy0">/</span>RouteSubscriber.php</pre></div></div> And the file contents are: <br /><strong>RouteSubscriber.php</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><br /><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #FF8000">/**<br />* @file<br />* Contains \Drupal\mymodule\Routing\RouteSubscriber.<br />*/<br /><br /></span><span style="color: #007700">namespace </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">badzilla_static</span><span style="color: #007700">\</span><span style="color: #0000BB">Routing</span><span style="color: #007700">;<br /><br />use </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Core</span><span style="color: #007700">\</span><span style="color: #0000BB">Routing</span><span style="color: #007700">\</span><span style="color: #0000BB">RouteSubscriberBase</span><span style="color: #007700">;<br />use </span><span style="color: #0000BB">Symfony</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">Routing</span><span style="color: #007700">\</span><span style="color: #0000BB">RouteCollection</span><span style="color: #007700">;<br /><br /><br /><br /></span><span style="color: #FF8000">/**<br />* Listens to the dynamic route events.<br />*/<br /></span><span style="color: #007700">class </span><span style="color: #0000BB">RouteSubscriber </span><span style="color: #007700">extends </span><span style="color: #0000BB">RouteSubscriberBase </span><span style="color: #007700">{<br /><br />    </span><span style="color: #FF8000">/**<br />    * {@inheritdoc}<br />    */<br />    </span><span style="color: #007700">public function </span><span style="color: #0000BB">alterRoutes</span><span style="color: #007700">(</span><span style="color: #0000BB">RouteCollection $collection</span><span style="color: #007700">)<br />    {<br />        </span><span style="color: #FF8000">// Replace dynamically created "search.view_node_search" route's Controller with my own.<br />        </span><span style="color: #007700">if (</span><span style="color: #0000BB">$route </span><span style="color: #007700">= </span><span style="color: #0000BB">$collection</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">get</span><span style="color: #007700">(</span><span style="color: #DD0000">'search.view_node_search'</span><span style="color: #007700">)) {<br />            </span><span style="color: #0000BB">$route</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">setDefault</span><span style="color: #007700">(</span><span style="color: #DD0000">'_controller'</span><span style="color: #007700">, </span><span style="color: #DD0000">'\Drupal\badzilla_static\Controller\BadzillaStaticSearchController::view'</span><span style="color: #007700">);<br />        }<br />    }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div> Here I am replacing the built-in search module's controller with my own. Now let's build out my own do-nothing controller.</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Custom search controller</div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">As previously, create the file structure. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ <span class="kw2">mkdir</span> src<span class="sy0">/</span>Controller $ <span class="kw2">touch</span> src<span class="sy0">/</span>Controller<span class="sy0">/</span>BadzillaStaticSearchController.php</pre></div></div> Here's the controller in all its glory.<br /><strong>BadzillaStaticSearchController.php</strong> <div class="codeblock geshifilter"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">namespace </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">badzilla_static</span><span style="color: #007700">\</span><span style="color: #0000BB">Controller</span><span style="color: #007700">;<br /><br />use </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">search</span><span style="color: #007700">\</span><span style="color: #0000BB">SearchPageInterface</span><span style="color: #007700">;<br />use </span><span style="color: #0000BB">Symfony</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">HttpFoundation</span><span style="color: #007700">\</span><span style="color: #0000BB">Request</span><span style="color: #007700">;<br />use </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">search</span><span style="color: #007700">\</span><span style="color: #0000BB">Controller</span><span style="color: #007700">\</span><span style="color: #0000BB">SearchController</span><span style="color: #007700">;<br /><br /></span><span style="color: #FF8000">/**<br /> * Override the Route controller for search.<br /> */<br /></span><span style="color: #007700">class </span><span style="color: #0000BB">BadzillaStaticSearchController </span><span style="color: #007700">extends </span><span style="color: #0000BB">SearchController </span><span style="color: #007700">{<br /><br />    </span><span style="color: #FF8000">/**<br />     * {@inheritdoc}<br />     */<br />    </span><span style="color: #007700">public function </span><span style="color: #0000BB">view</span><span style="color: #007700">(</span><span style="color: #0000BB">Request $request</span><span style="color: #007700">, </span><span style="color: #0000BB">SearchPageInterface $entity</span><span style="color: #007700">)<br />    {<br />        </span><span style="color: #0000BB">$build </span><span style="color: #007700">= [];<br />        </span><span style="color: #0000BB">$plugin </span><span style="color: #007700">= </span><span style="color: #0000BB">$entity</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getPlugin</span><span style="color: #007700">();<br />        </span><span style="color: #0000BB">$build</span><span style="color: #007700">[</span><span style="color: #DD0000">'#title'</span><span style="color: #007700">] = </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">suggestedTitle</span><span style="color: #007700">();<br /><br />        </span><span style="color: #0000BB">$results </span><span style="color: #007700">= [];<br />        </span><span style="color: #0000BB">$build</span><span style="color: #007700">[</span><span style="color: #DD0000">'search_results'</span><span style="color: #007700">] = [<br />            </span><span style="color: #DD0000">'#theme' </span><span style="color: #007700">=&gt; [<br />                </span><span style="color: #DD0000">'item_list__search_results__' </span><span style="color: #007700">. </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getPluginId</span><span style="color: #007700">(),<br />                </span><span style="color: #DD0000">'item_list__search_results'</span><span style="color: #007700">,<br />            ],<br />            </span><span style="color: #DD0000">'#items' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$results</span><span style="color: #007700">,<br />            </span><span style="color: #DD0000">'#empty' </span><span style="color: #007700">=&gt; [<br />                </span><span style="color: #DD0000">'#markup' </span><span style="color: #007700">=&gt; </span><span style="color: #DD0000">''</span><span style="color: #007700">,<br />            ],<br />            </span><span style="color: #DD0000">'#list_type' </span><span style="color: #007700">=&gt; </span><span style="color: #DD0000">'ol'</span><span style="color: #007700">,<br />            </span><span style="color: #DD0000">'#context' </span><span style="color: #007700">=&gt; [<br />                </span><span style="color: #DD0000">'plugin' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getPluginId</span><span style="color: #007700">(),<br />            ],<br />        ];<br /><br />        return </span><span style="color: #0000BB">$build</span><span style="color: #007700">;<br />    }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div> The view function must return a render array, and so I have honoured the convention of the parent SearchController::view to large extent although obviously in my case there will always be no results returned. A useful stub!</div> </div> </div> <div class="field--item"> <div class="paragraph paragraph--type--blog-heading-picture-text paragraph--view-mode--default"> <div class="field field--name-field-heading field--type-string field--label-hidden field--item">Clear Rebuild and Test</div> <div class="field field--name-field-blog-image field--type-image field--label-hidden field--items"> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-11/Screenshot_2018-11-18_at_15_22_08-edited.png?itok=SkBm2FM6 325w, /sites/default/files/styles/max_650x650/public/2018-11/Screenshot_2018-11-18_at_15_22_08-edited.png?itok=ckGbh1Np 650w, /sites/default/files/styles/max_1300x1300/public/2018-11/Screenshot_2018-11-18_at_15_22_08-edited.png?itok=a925Zvff 1300w, /sites/default/files/styles/max_2600x2600/public/2018-11/Screenshot_2018-11-18_at_15_22_08-edited.png?itok=9SaizRUC 2314w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-11/Screenshot_2018-11-18_at_15_22_08-edited.png?itok=SkBm2FM6" alt="Seach1" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field--item"> <img srcset="/sites/default/files/styles/max_325x325/public/2018-11/Screenshot_2018-11-18_at_15_21_23-edited.png?itok=Hx9JuUhC 325w, /sites/default/files/styles/max_650x650/public/2018-11/Screenshot_2018-11-18_at_15_21_23-edited.png?itok=uFQLs4hF 650w, /sites/default/files/styles/max_1300x1300/public/2018-11/Screenshot_2018-11-18_at_15_21_23-edited.png?itok=MapWzEyf 1300w, /sites/default/files/styles/max_2600x2600/public/2018-11/Screenshot_2018-11-18_at_15_21_23-edited.png?itok=5giBsaDr 2312w" sizes="(min-width: 1290px) 1290px, 100vw" src="/sites/default/files/styles/max_325x325/public/2018-11/Screenshot_2018-11-18_at_15_21_23-edited.png?itok=Hx9JuUhC" alt="Search2" typeof="foaf:Image" class="img-responsive" /> </div> </div> <div class="field field--name-field-blog-text field--type-text-long field--label-hidden field--item">Since we've added to our services yaml file we need to do the customary cache rebuild for our changes to take effect. <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">$ drush cr <span class="br0">[</span>success<span class="br0">]</span> Cache rebuild complete.</pre></div></div> Next let's attempt a search on the dynamic and the static version of my sandbox Badzilla sites. The two screenshots above show that both the static and the dynamic version of the sandbox sites are correctly showing a blank search page. Next we have to plug in the client....</div> </div> </div> </div> <div class="field field--name-field-blog-youtube field--type-entity-reference-revisions field--label-hidden field--items"> <div class="field--item"> <div class="paragraph paragraph--type--blog-text-youtube paragraph--view-mode--default"> </div> </div> </div> <div class="field field--name-field-blog-terms field--type-entity-reference field--label-inline"> <div class="field--label">blog terms</div> <span class="field__items"> <span class="field--item"><a href="/static-site" hreflang="en">Static Site</a></span> <span class="field--item"><a href="/drupal8" hreflang="en">Drupal 8</a></span> <span class="field--item"><a href="/development" hreflang="en">Development</a></span> <span class="field--item"><a href="/drupal" hreflang="en">Drupal</a></span> <span class="field--item"><a href="/php" hreflang="en">PHP</a></span> </span> </div> Sun, 18 Nov 2018 10:04:27 +0000 nigel 159 at http://badzilla.co.uk