<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Zo kan het toch.]]></title><description><![CDATA[IT-oplossingen voor problemen waar ik tegenaan ben gelopen. Bijna altijd Ockham style opgelost.]]></description><link>https://www.timhuesken.nl/</link><image><url>https://www.timhuesken.nl/favicon.png</url><title>Zo kan het toch.</title><link>https://www.timhuesken.nl/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Thu, 15 Jan 2026 09:58:02 GMT</lastBuildDate><atom:link href="https://www.timhuesken.nl/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Propvolle Gmail automatisch laten opruimen]]></title><description><![CDATA[Heb jij ook een volle Gmail? Tijd voor grote schoonmaak, maar geen zin om het zelf te doen? Met een script kan je het dagelijks/maandelijks door de computer zelf laten doen.]]></description><link>https://www.timhuesken.nl/gmail-en-propvolle-reclame-oude-e-mails-opschonen-met-een-script/</link><guid isPermaLink="false">62a224247b4523000101d78d</guid><category><![CDATA[Google Script]]></category><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 05 Dec 2019 10:57:32 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1516992654410-9309d4587e94?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1516992654410-9309d4587e94?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Propvolle Gmail automatisch laten opruimen"><p>Misschien een beetje een verwarrende titel. Even uiteenzetten, want dan begrijp je beter waar ik het over heb. Voor mensen die een Gmail adres hebben, zullen vast wel bekend zijn met de volle inbox. En dan met name de reclame mails en updates van allerlei diensten (track en trace code mailtjes en bestellingsbevestigingen). Stel nu, dat je net zoals ik, meestal niet meteen die mailtjes wil weggooien of archiveren. En het dan vergeet, of de moeite niet neemt om het op te ruimen... <br><br>Dus, KADOOTJE VAN DE SINT omdat het vandaag 5 december is (<a href="#link">link</a> naar mijn oplossing onderin deze blogpost &#x1F381;)</p><h2 id="volle-inboxmeldingen">Volle inboxmeldingen</h2><p>Na een aantal jaar heeft ook Google wel genoeg van al die mails van je die je maar &apos;bewaard&apos;. Dus kreeg ik meldingen dat mijn inbox toch echt wel een beetje heel vol zat. Nu ben ik een beetje een luie ITer, en was nog maar net begonnen om handmatig op te gaan ruimen, of ik had er alweer genoeg van.</p><h2 id="dat-kan-makkelijker">Dat kan makkelijker</h2><p>Ik dacht, dit moet toch makkelijker kunnen? Ik wil in feite prima van oude mailtjes af, maar zoals ik al zei, die online shopping bestellingen en de mailtjes ervan zijn toch meestal nog wel handig wanneer het op een retour aan komt. Dus die van afgelopen maand wil ik nog wel houden. Alles van daarvoor kan in principe wel weg. Gelukkig heb ik al een tijdje geleden in Gmail aangezet dat ik aparte categorie&#xEB;n voor bepaalde mailtjes wil hebben, en heb Gmail inmiddels wel kunnen trainen welke mailtjes reclame zijn, welke updates (dus bestellingen/track+trace/etc.) zijn en welke gewoon netjes in mijn Postvak In moeten komen.</p><p>Dit en de zoekfunctie in Gmail maken het best eenvoudig om een goede selectie te krijgen van de mailtjes die wel weg kunnen. Daarna klik ik op alles selecteren en gooi het weg. </p><h2 id="opgeruimd-staat-netjes">Opgeruimd staat netjes</h2><p>Zo, alles eens even goed opgeruimd in mijn mailbox. En dat fijne opgeruimde gevoel was helaas maar voor korte duur. De nieuwe reclame mailtjes en updates kwamen alweer binnen. Dat was het moment dat ik dacht, dit wil ik niet iedere dag/maand gaan doen, of iedere x jaar op het moment dat het te vol zit.</p><h2 id="is-dit-te-automatiseren">Is dit te automatiseren?</h2><p>Ik ben wat gaan Googlen want ik hou niet van repetitieve handelingen die een computer prima zelf kan zolang ik de juiste instructies en randvoorwaarden stel.</p><p>Gelukkig is er tegenwoordig Google Script (<a href="https://script.google.com">https://script.google.com</a>)! &#x1F44C;&#x1F389;</p><p>Je kan er zelf een script maken, en door je functie in het script daarna aan een trigger te hangen, het automatisch periodiek laten uitvoeren!</p><p>Super chill, dit is wat ik zocht. Ik heb een script gemaakt voor elke verschillende inbox, want reclame mag best maandelijks en tot een maand terug worden opgeschoond, maar de updates van bestellingen vind ik wel prima als die maandelijks tot een jaar terug worden bewaard.</p><h2 id="in-het-kort">In het kort</h2><ol><li>Ga naar Gmail &gt; Instellingen &gt; Inbox en zet de vinkjes aan voor de categorie&#xEB;n.<br></li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.timhuesken.nl/content/images/2019/12/afbeelding.png" class="kg-image" alt="Propvolle Gmail automatisch laten opruimen" loading="lazy"><figcaption>De eerste stap, categorie&#xEB;n aanzetten in Gmail.</figcaption></figure><!--kg-card-begin: html--><a name="stap2"></a><!--kg-card-end: html--><p>2. Ga naar de zoekbalk om wat zoekopdrachten te maken die de set van mailtjes teruggeven die jij periodiek wilt opschonen.<br></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.timhuesken.nl/content/images/2019/12/afbeelding-1.png" class="kg-image" alt="Propvolle Gmail automatisch laten opruimen" loading="lazy"><figcaption>Dit is de zoekopdracht voor alle mails binnen de categorie reclames. Kopieer het uiteindelijke resultaat even naar je kladblok of klembord, want dit gebruiken we straks ook in het script om de juiste mails te gaan verwijderen.</figcaption></figure><p>3. Ga naar <a href="https://script.google.com">script.google.com</a> en log in.</p><p>4. Maak een nieuw project aan.</p><p>5. Gebruik deze code bijvoorbeeld als basis:<br></p><pre><code class="language-javascript">function cleanUpReclame() {
  var delayDays = 30 // Aantal dagen waarna mails naar de vuilnisbak gaan
  var maxDate = new Date();
  maxDate.setDate(maxDate.getDate()-delayDays);
  var threads = GmailApp.search(&apos;category:promotions is:unread&apos;);
  for (var i = 0; i &lt; threads.length; i++) {
      if (threads[i].getLastMessageDate()&lt;maxDate)
      {
        threads[i].moveToTrash();
      }
  }
}
function cleanUpUpdates() {
  var delayDays = 364 // Aantal dagen waarna mails naar de vuilnisbak gaan
  var maxDate = new Date();
  maxDate.setDate(maxDate.getDate()-delayDays);
  var threads = GmailApp.search(&apos;category:updates&apos;);
  for (var i = 0; i &lt; threads.length; i++) {
      if (threads[i].getLastMessageDate()&lt;maxDate)
      {
        threads[i].moveToTrash();
      }
  }
}

function cleanUpForums() {
  var delayDays = 364 // Aantal dagen waarna mails naar de vuilnisbak gaan
  var maxDate = new Date();
  maxDate.setDate(maxDate.getDate()-delayDays);
  var threads = GmailApp.search(&apos;category:forums&apos;);
  for (var i = 0; i &lt; threads.length; i++) {
      if (threads[i].getLastMessageDate()&lt;maxDate)
      {
        threads[i].moveToTrash();
      }
  }
}

function cleanUpSocial() {
  var delayDays = 364 // Aantal dagen waarna mails naar de vuilnisbak gaan
  var maxDate = new Date();
  maxDate.setDate(maxDate.getDate()-delayDays);
  var threads = GmailApp.search(&apos;category:social&apos;);
  for (var i = 0; i &lt; threads.length; i++) {
      if (threads[i].getLastMessageDate()&lt;maxDate)
      {
        threads[i].moveToTrash();
      }
  }
}</code></pre><p>6. Maak je keuzes even bij de variabelen (delayDays en de .search(&apos;category:xxx&apos;);<br>Je kan namelijk de functie gewoon kopi&#xEB;ren zoveel je wil, even een andere naam geven bij de function declaratie, en met name de zoekopdracht bij deze regel kan je veel bereiken:</p><pre><code class="language-javascript">var threads = GmailApp.search(&apos;category:promotions is:unread&apos;);</code></pre><p>Zo heb je bijvoorbeeld, en in mijn voorbeeld hier heb ik er ook bij staan: <code>is:unread</code>. Dit komt gewoon van de zoekopdrachten die je kan maken bij <a href="#stap2">stap 2</a> hierboven. Zo kan je er ook nog bepaalde afzenders bij zetten etc., bijvoorbeeld:</p><pre><code class="language-javascript">var threads = GmailApp.search(&apos;category:promotions is:unread from:(info@email.ibood.com OR newsletter@outspot.nl&apos;);</code></pre><p>Dit gooit alle mails van ibood OF outspot van het type reclame weg die nog niet zijn gelezen. Hier kan je dus leuke selecties maken voor bepaalde mailtjes. Vaak zijn aanbiedingen tijdelijk geldig dus ja. Als je het mailtje dan na 2 dagen nog niet hebt gelezen bijvoorbeeld, kan je het wel weggooien.</p><p>7. We zijn er bijna, nu niet vergeten de trigger in te stellen. <br>Wanneer je het stukje code hebt gemaakt en hebt opgeslagen kan je teruggaan naar je project en je zal je functie zien terugkomen in het overzichtje. Klik op Triggers:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.timhuesken.nl/content/images/2019/12/afbeelding-7.png" class="kg-image" alt="Propvolle Gmail automatisch laten opruimen" loading="lazy"><figcaption>Triggers instellen voor je script</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.timhuesken.nl/content/images/2019/12/afbeelding-8.png" class="kg-image" alt="Propvolle Gmail automatisch laten opruimen" loading="lazy"><figcaption>Trigger aanmaken of bestaande wijzigen</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.timhuesken.nl/content/images/2019/12/afbeelding-9.png" class="kg-image" alt="Propvolle Gmail automatisch laten opruimen" loading="lazy"><figcaption>Kies per actie wanneer je de functie wil laten uitvoeren</figcaption></figure><p>Hier kan je de door jouw gekozen functienamen periodiek laten uitvoeren. Dit is het laatste en daarna ben je voorlopig klaar met je mailbox. Nou ja, wanneer het om opruimen gaat dan.</p><!--kg-card-begin: html--><a name="link"></a><!--kg-card-end: html--><h2 id="tot-slot">Tot slot</h2><p>Omdat Google helemaal van het delen van dit soort handige dingen is tegenwoordig, hier de link om de code even makkelijk over te nemen naar je eigen account: <a href="https://script.google.com/d/1OfbhSmPbagoAaCVvhsccDssVk6L-84OBc7pa9ewt60Cs-r4nNTnp9pNJ/edit?usp=sharing">https://script.google.com/d/1OfbhSmPbagoAaCVvhsccDssVk6L-84OBc7pa9ewt60Cs-r4nNTnp9pNJ/edit?usp=sharing</a></p><h2 id="vragen-mag">Vragen mag</h2><p>Laat hieronder een reactie achter en ik zal je proberen te helpen als het niet lukt. &#x1F60E;</p>]]></content:encoded></item><item><title><![CDATA[reCAPTCHA 3: Je hoort em niet, maar ziet em wel. Een klein beetje dan.]]></title><description><![CDATA[Ik ben geen robot. Ben je het ook zat? Tot 3 keer toe invullen terwijl je geen fout maakt? Gelukkig is dat verleden tijd! Er is een nieuwe versie van reCAPTCHA. Is deze dan wel geweldig?]]></description><link>https://www.timhuesken.nl/recaptcha-v3-implementeren/</link><guid isPermaLink="false">62a224247b4523000101d78c</guid><category><![CDATA[recaptcha]]></category><category><![CDATA[spam]]></category><category><![CDATA[javascript]]></category><category><![CDATA[php]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 22 Nov 2018 13:49:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1533915828531-55b274d98dc5?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=4231bdbf154d24ca9fc9497755abb1f8" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1533915828531-55b274d98dc5?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=4231bdbf154d24ca9fc9497755abb1f8" alt="reCAPTCHA 3: Je hoort em niet, maar ziet em wel. Een klein beetje dan."><p>Ben jij geen robot? Ik ook niet. Toch moet ik het keer op keer bewijzen. We kennen het vast wel inmiddels. Formulier invullen? Ik ben geen robot aanvinken. Wat mij opvalt, is dat het tegenwoordig bijna altijd tot het oplossen van een puzzel leidt. Ik krijg vrijwel nooit meer direct een groen vinkje! Was het maar een leuke bezigheid. Nee, alleen maar irritant. En het meest irritante nog wel: Je lost em op, je weet zeker dat je het goed hebt. Nee hoor, fout, doe er nog maar 1. Dat met die auto&apos;s op de foto en bussen en verkeersborden etc. Na 2 keer mag je dan toch door. Fijn hoor.</p><p>Vanuit mijn werk is het ook nog eens helemaal irritant voor onze bezoekers met een iOS apparaat. Er zit een bug in de code van reCAPTCHA v2, waarbij de <a href="https://github.com/google/recaptcha/issues/130">pagina lekker naar beneden wordt gescrollt</a>. Poef, daar zit je dan in de footer van de website. Succes ermee... Zo gaat je conversieratio wel naar de knoppen ja.</p><p>Deze bug bestaat al een aantal jaar, en doet zich bij verschillende versies van iOS voor. Soms is het er wel, dan weer niet, dan weer wel. Denk je dat Google er iets aan doet? Nee hoor. Je kan met wat suggesties van de github community wel wat <a href="https://github.com/google/recaptcha/issues/130#issuecomment-362308382">scriptjes</a> proberen, maar die bleken niet te helpen bij de recente versie van iOS. Hieronder een voorbeeld oplossing:</p><pre><code class="language-javascript">var HEADER_HEIGHT = 0; // Height of header/menu fixed if exists
var isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
var grecaptchaPosition;

var isScrolledIntoView = function (elem) {
  var elemRect = elem.getBoundingClientRect();
  var isVisible = (elemRect.top - HEADER_HEIGHT &gt;= 0 &amp;&amp; elemRect.bottom &lt;= window.innerHeight);

  return isVisible;
};

if (isIOS) {
  var recaptchaElements = document.querySelectorAll(&apos;.g-recaptcha&apos;);

  window.addEventListener(&apos;scroll&apos;, function () {
    Array.prototype.forEach.call(recaptchaElements, function (element) {
      if (isScrolledIntoView(element)) {
        grecaptchaPosition = document.documentElement.scrollTop || document.body.scrollTop;
      }
    });
  }, false);
}

var onReCaptchaSuccess = function () {
  if (isIOS &amp;&amp; grecaptchaPosition !== undefined) {
    window.scrollTo(0, grecaptchaPosition);
  }
};</code></pre><pre><code class="language-css">&lt;div class=&quot;g-recaptcha&quot; data-theme=&quot;light&quot; data-type=&quot;image&quot; data-sitekey=&quot;XXXXXX&quot;&gt;&lt;/div&gt;
</code></pre><p>Mocht dit nu niet je probleem verhelpen, dan is er altijd nog reCAPTCHA v3! (of invisible reCAPTCHA v2).</p><p>Deze nieuwe versie is eind Oktober 2018 uit de beta gekomen en bevat geen vinkvakjes meer en captcha challenges zoals we deze maar al te goed kennen.</p><p>Het enige wat deze versie aan irritatiefactor heeft, net als de invisble variant van versie 2, is die badge van Google die vertoont &apos;moet&apos; worden. Dat staat in de gebruikersovereenkomst.</p><p>Ik snap het wel vanuit Google, maar dat ding zit op de meest irritante plekken, waar je vaak ook al andere knoppen op je site hebt gehangen. Ja, je kan het aanpassen door de badge <code>data-badge=inline</code> mee te geven, dat klopt, echter dat werkt alleen bij versie v2 met de invisible captcha. En die bevat nog van die puzzels/challenges.</p><p>Maar wacht, versie 3 werkt dus anders en heeft geen challenges die de gebruiker lastig vallen! AWESOME, toch? Ja, maar alleen de badge inline werkt(e) een paar weken geleden (nog) niet. Laat dat nou net die irritatie bij mij zijn haha.</p><p>Ik wil die badge gewoon inline kunnen stylen en ergens in de footer dumpen.</p><p>Nah ja, helaas. Wat wel goed is aan versie 3, is dat je dus geen reCAPTCHA challenges meer krijgt. Hoe werkt dat dan? Je draait gewoon een stukje code met een specifieke reCAPTCHA sleutel die je voor versie 3 hebt aangemaakt in de <a href="https://www.google.com/recaptcha/admin#list">reCAPTCHA admin</a> sectie.</p><p>Hierdoor weet Google eigenlijk al welke versie er moet worden aangeboden. En nu het vernuftige aan deze versie: Google monitort het gedrag van de bezoeker op jouw aangeven, en analyseert dit op de achtergrond. Zodra jij een validatie doet richting reCAPTCHA (dus bijvoorbeeld wanneer er op verzenden wordt geklikt bij een formulier), dan heeft Google in de tussentijd het gedrag al in de gaten gehouden en geeft een score tussen de 0 en 1 terug. Jij kan dan aangeven, als het een 0.5 of lager is, dan moet er geen formulier verzonden worden. Google weet met 0.9 eigenlijk zo goed als zeker dat het geen spam-bot is. Bij 0.3 zou je toch je moeten afvragen of dit niet een spam-bot is. <br><br>Mooi, handig, makkelijk, geen last van als gebruiker, maar wel een beetje kantje boord met privacy en het idee dat je gedragingen nog meer gemonitort worden door Google. En dat laatste maakt het een vernuftige zet van Google. Zij meer data, jij minder irritatie.</p><p>Behalve dan die BADGE! Okay, die is persoonlijk...</p><p>Goed, hoe doe je dit dan in de praktijk? De aangeleverde code is niet geweldig, en de dev-docs zijn ook nog niet helemaal op orde, maar hieronder hoe je het in werking zet:</p><p>Allereerst moet je sowieso de recaptcha api aanroepen op je pagina&apos;s: </p><pre><code class="language-html">&lt;script src=&apos;https://www.google.com/recaptcha/api.js?render=&lt;jouw publieke v3 recaptcha key&gt;&apos;&gt;&lt;/script&gt;</code></pre><p>Daarna moet je ervoor zorgen dat er zodra het ingeladen is, een pageview wordt doorgegeven:</p><pre><code class="language-javascript">&lt;script&gt;
  grecaptcha.ready(function() {
      grecaptcha.execute(&apos;&lt;jouw publieke v3 recaptcha key&gt;&apos;, {action: &apos;homepage&apos;}).then(function(token) {
         ...
      });
  });
  &lt;/script&gt;</code></pre><p>Ik raad dan aan een action:&apos;pageview&apos; mee te geven op wanneer je een CMS hebt dat met templates werkt. En een &apos;homepage&apos; op alleen de homepage).</p><p>Waar de drie puntjes staan, ..., kan je dan een validatie POST functie plaatsen of aanroepen wanneer het nodig is dat er gecontroleerd wordt. Dit controleren doe je om in te grijpen. </p><p>Dan denk ik dus als meest voor de hand liggende controle moment, bij het verzenden van een formulier. Dat zal ik hieronder dan nog even verder uitwerken. Waar je ook aan kan denken, is bij het klikken van een hyperlink. Nou zullen bots die niet per se een click event triggeren, maar je zou wellicht wel een trigger kunnen schrijven voor dit soort spiders, en ze daarmee blokkeren. Moet je alleen niet bij de goedaardige spiders doen die je content ranken &#x1F609;</p><p>Anyways, hier een mogelijke functie voor bij een click op de submit button:</p><pre><code class="language-javascript">document.getElementById(&quot;btnSubmit&quot;).addEventListener(&quot;click&quot;, function (e) {
    e = &quot;&lt;public key hier invullen&gt;&quot;;
    grecaptcha.ready(function () {
        grecaptcha.execute(e, {
            action: &quot;submit&quot;
        }).then(function (e) {
            jQuery.ajax({
                type: &quot;POST&quot;,
                url: &quot;https://www.jouwdomein.nl/recaptcha-verify-v3.php?token=&quot; + e,
                data: JSON.stringify(e),
                dataType: &quot;json&quot;,
                success: function (e) {
                    if (&quot;true&quot; == e.success) {
                        var t = document.getElementsByTagName(&quot;form&quot;)[0];
                        t.setAttribute(&quot;id&quot;, t.name), document.getElementById(t.name).submit()
                    }
                },
                error: function (e, t, n) {
                    console.log(n)
                }
            })
        })
    })
});</code></pre><p>De jQuery AJAX call post de info naar een php bestand wat je kan gebruiken om via Google een score terug te krijgen. <a href="https://github.com/google/recaptcha/tree/master/examples">Deze zijn gewoon te krijgen op github</a>, en kan je gebruiken en een beetje ombouwen:</p><pre><code class="language-php">&lt;?php
/**
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      https://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the &quot;Software&quot;), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
require __DIR__ . &apos;/appengine-https.php&apos;;
// Initiate the autoloader. The file should be generated by Composer.
// You will provide your own autoloader or require the files directly if you did
// not install via Composer.
require_once __DIR__ . &apos;/../vendor/autoload.php&apos;;
// Register API keys at https://www.google.com/recaptcha/admin
$siteKey = &apos;&apos;;
$secret = &apos;&apos;;
// Copy the config.php.dist file to config.php and update it with your keys to run the examples
if ($siteKey == &apos;&apos; &amp;&amp; is_readable(__DIR__ . &apos;/config.php&apos;)) {
    $config = include __DIR__ . &apos;/config.php&apos;;
    $siteKey = $config[&apos;v3&apos;][&apos;site&apos;];
    $secret = $config[&apos;v3&apos;][&apos;secret&apos;];
}
// Effectively we&apos;re providing an API endpoint here that will accept the token, verify it, and return the action / score to the page
// In production, always sanitize and validate the input you retrieve from the request.
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
$resp = $recaptcha-&gt;setExpectedHostname($_SERVER[&apos;SERVER_NAME&apos;])
                  -&gt;setExpectedAction($_GET[&apos;action&apos;])
                  -&gt;setScoreThreshold(0.5)
                  -&gt;verify($_GET[&apos;token&apos;], $_SERVER[&apos;REMOTE_ADDR&apos;]);
header(&apos;Content-type:application/json&apos;);
echo json_encode($resp-&gt;toArray());</code></pre><p>Let wel dat je dus nog even in de config.php je keys moet aanleveren. Hier wordt ook de secret key van reCAPTCHA gebruikt om het gedrag te verifi&#xEB;ren. de <code>setScoreThreshold(0.5)</code> is dan iets waar je mee kan spelen. Zet em wat lager en je weet zeker dat alle mensen er in ieder geval doorkomen. Sommige spam zou ook nog door kunnen komen. Zet em hoger en mensen met automatische formulier invullers komen misschien niet zomaar er doorheen.</p><p>Dusssss je hoort em niet, maar ziet em wel, een beetje dan... door die lelijke badge (Ja ik weet hoe ik het kan manipuleren, maar goed <s>dat is niet netjes en hoort niet</s>).</p><!--kg-card-begin: markdown--><p><mark>UPDATE (17-4-2019): Inmiddels mag je van Google de batch verbergen, mits je maar de disclaimer en privacy linkjes en teksten in de user flow plaatst. Dat is toch wel een stukje fijner dacht ik zo!</mark></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Mijn huidige setup]]></title><description><![CDATA[Van xpenology naar unRAID. Waarom unRAID en welke andere opties waren er? Ik loop er doorheen en geef wat starters tips voor unRAID. Must have plugins bijvoorbeeld.]]></description><link>https://www.timhuesken.nl/mijn-huidige-setup/</link><guid isPermaLink="false">62a224247b4523000101d78a</guid><category><![CDATA[unRAID]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 13 Sep 2018 17:13:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2018/09/unraid-stacked-dark-7.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.timhuesken.nl/content/images/2018/09/unraid-stacked-dark-7.png" alt="Mijn huidige setup"><p>Ik heb recentelijk mijn hardware van een update voorzien (betere processor, meer geheugen en aardig wat meer opslagruimte). Ook ben ik afgestapt van xpenology als besturingssysteem. Het was te afhankelijk van hobbyisten voor updates en het duurt tegenwoordig meer dan een jaar voordat Synology een keer de broncode vrij geeft. </p><h3 id="welk-besturingssysteem-dan">Welk besturingssysteem dan?</h3><p>Ik heb een tijd rondgekeken en ervaringen van anderen gelezen over verschillende opties:</p><ul><li>ESXi, ProxMox, Hypervisor en dergelijken</li><li>Open Media Vault, Amahi etc.</li><li>FreeNAS</li><li>Standaard Linux server distro&apos;s - dus veel voorwerk zelf doen</li><li>unRAID</li></ul><p>Ik ben uiteindelijk bij unRAID terecht gekomen. De redenen zijn eigenlijk eenvoudig, als je naar mijn eisenpakket kijkt:</p><ul><li>Native Docker</li><li>Klassieke NAS functionaliteiten</li><li>Doorvoer van data zo snel als mijn internet verbinding</li></ul><p>unRAID bood al deze mogelijkheden, daar waar de anderen gevirtualiseerde Docker implementaties hadden, of heel erg gericht waren op RAID configuraties, of op basis van Virtual Machines draaiden. </p><p>FreeNAS, OMV en andere Linux Distro&apos;s: Striped RAID Arrays vond ik geen must, en dat is bij unRAID dan ook niet mogelijk, ook al zou ik daar veel hogere data transfer snelheden halen.</p><p>Omdat FreeNAS daarnaast ook nog eens gebaseerd op FreeBSD is, en met Jails werkt (een soort plugin idee), en Docker dus via een virtual machine aanbiedt, evenals OMV, welke wel gewoon een linux distro is, kreeg ik het idee dat het beheren van mijn docker containers niet het meest effici&#xEB;nt zou zijn. Puur omdat er geen native version van Docker voor FreeBSD bestaat.</p><p>ESxi, ProxMox etc.: Te bare metal voor mij. Ik wil niet allerlei exotische besturingssystemen in virtual machines plaatsen en daarbij ook nog eens processoren en geheugen evenals controllers toe te wijzen, ook al is mijn hardware daar inmiddels toe instaat met Intel VT-d etc. Teveel gedoe, wanneer het gros wat ik nodig heb en wil toch gewoon via docker containers zal zijn, en geen behoefte heb om met die exotische besturingssystemen aan de slag te gaan.</p><p>En mocht ik dan toch zoiets nodig hebben, ik kan altijd nog een beetje met een virtual machine spelen via unRAID.</p><h3 id="dus-unraid-en-nu">Dus unRAID, en nu?</h3><p>Installeren was best eenvoudig, gewoon zoals gewend bij xpenology een USB flashen met wat bestanden via een installer, en opstarten maar.</p><p>Meer was het eigenlijk niet. Gewoon wat instellingen doen via de webUI van unRAID, wat shares maken, een paar plugins installeren, en mijn nieuwe NAS stond.</p><p>Ik kan je wel een aantal plugins aanraden, die het unRAID-leven wat makkelijker maken.</p><p>Begin met onderstaande, de rest is daarna met een paar klikken aan te vullen/installeren: </p><ul><li><a href="https://forums.unraid.net/topic/38582-plug-in-community-applications/">Community Applications</a></li></ul><p>Hierna kun je via het tabblad <code>Apps</code>, wat erbij komt, gemakkelijk zoeken naar de volgende plugins:</p><ul><li>CA Auto Turbo Write Mode (voor veel snellere data-transfers)</li><li>CA Auto Update Applications</li><li>CA Backup / Restore Appdata</li><li>CA Cleanup Appdata</li><li>CA Config Editor</li><li>CA Docker Autostart Manager</li><li>CA Mover Tuning</li><li>Dynamix SSD TRIM (als je een SSD als cache drive hebt)</li><li>Fix Common Problems (handig om je unRAID probleemloos te houden)</li><li>Preclear Disks (om nieuwe schijven voor te bereiden en ze toe te voegen aan je array)</li><li>Unassigned Devices (om externe USB schijven te mounten en beschikbaar te maken die geen onderdeel van je array vormen)</li></ul><p>Daarna is het volle bak Docker containers, die je overigens ook terugvindt in de Community Applications plugin, en dus gewoon kunt zoeken via <code>Apps</code>.</p><p>Het enige aan unRAID is, dat het niet gratis is, en misschien is het niet voor iedereen. Ik denk dan aan mensen die per se striped RAID arrays zoeken, of hele virtual machines willen draaien, zonder dat er een Linux based besturingssysteem voor zit, en dus eigenlijk baremetal willen draaien met oplossingen als ESXi of Hypervisor, etc.</p><p>Ik wil op dit moment echt niets anders meer. Dit was zo de moeite waard om up te graden t.o.v. xpenology.</p><p>Dit en andere blogs draai ik erop, allemaal in eigen containers. Mijn media toepassingen ook allemaal in hun eigen container. Updaten van de software binnen de containers gebeurt elke nacht, wanneer er een update beschikbaar is. Alle naar buiten gerichte WebUI&apos;s zijn van TLS/SSL voorzien, en goed afgeschermd qua poorten binnen het interne netwerk.</p><blockquote>Heerlijk.</blockquote>]]></content:encoded></item><item><title><![CDATA[Met 1 commando je eigen SSL-beveiligde Ghost blog opstarten]]></title><description><![CDATA[Met 1 gechainde command kan je een zo een nieuw ghost blog starten. Vereisten? Docker en een domein dat naar je server wijst en een router met ports forwarded. Meer niet!]]></description><link>https://www.timhuesken.nl/met-1-commando-je-eigen-blog-opstarten/</link><guid isPermaLink="false">62a224247b4523000101d78b</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Nginx]]></category><category><![CDATA[Synology]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Terminal]]></category><category><![CDATA[SSH]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 13 Sep 2018 15:16:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2018/09/putty-ssh-terminal-1515x1028-xpMOZ9lj.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://www.timhuesken.nl/content/images/2018/09/putty-ssh-terminal-1515x1028-xpMOZ9lj.jpg" alt="Met 1 commando je eigen SSL-beveiligde Ghost blog opstarten"><p>Nou ja, het is een setje commando&apos;s en er zijn wat vereisten vooraf waar je aan moet voldoen. Maar als je die hebt, kun je dit kopi&#xEB;ren, kleine aanpassingen doen, plakken en uitvoeren. </p><!--kg-card-begin: markdown--><h2 id="devereisten">De vereisten</h2>
<ul>
<li>Je moet een computer hebben, die verbonden is aan het internet</li>
<li>Er moet linux op draaien <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></li>
<li>Er moet docker op ge&#xEF;nstalleerd zijn</li>
<li>Je router moet je even goed instellen qua port forwarding</li>
<li>Je hebt een terminal draaien en voldoende rechten</li>
<li>Je hebt een domeinnaam in bezit en die laat je verwijzen naar je eigen ip-adres</li>
</ul>
<p>Als je dit allemaal voor elkaar hebt (of bijvoorbeeld een Synology server hebt met versie 5.2 of hoger), plak je de volgende code, nadat je die gepersonaliseerd hebt, in de terminal:</p>
<pre><code class="language-bash">docker run -d --name nginx -p 8080:80 -p 4433:443 -v /path/to/docker/nginx/certs:/etc/nginx/certs:ro -v /path/to/docker/nginx/vhost:/etc/nginx/vhost.d:rw -v /var/run/docker.sock:/tmp/docker.sock:ro -v /path/to/docker/nginx/htpasswd:/etc/nginx/htpasswd:rw -v /path/to/docker/nginx/html:/usr/share/nginx/html:rw --restart=always jwilder/nginx-proxy

docker run --name nginx-letsencrypt -d \
    -v path/to/docker/nginx/certs:/etc/nginx/certs:rw \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    --volumes-from nginx \
    jrcs/letsencrypt-nginx-proxy-companion

docker run --name jouwghostblog -d -p 99:2368 \
-e VIRTUAL_HOST=www.jouwdomeinnaam.nl,jouwdomeinnaam.nl -e VIRTUAL_PORT=99 -e \
LETSENCRYPT_HOST=www.jouwdomeinnaam.nl,jouwdomeinnaam.nl \
-e LETSENCRYPT_EMAIL=jouw-emailadres.com \
-e url=https://jouwdomeinnaam.nl \
-e NODE_ENV=production \
-v /path/to/docker/ghost/jouwghostblog:/var/lib/ghost/content \
--restart=always \
ghost:alpine
</code></pre>
<p>En that&apos;s it. Verander dus even de gewenste poorten, misschien wil je bij de nginx container -p 80:80 en -p 443:443 hebben, dat kan (behalve bij Synology, gebruik dan de instellingen in de code hierboven qua poorten. Verander www.jouwdomeinnaam.nl naar je eigen domein-naam, en vul je e-mailadres even in voor letsencrypt.</p>
<p>Binnen luttele seconden heb je een ssl-beveiligd ghost blog klaar staan en kan je gaan schrijven.</p>
<p>Dus nog 1 keer in de herhaling:</p>
<ol>
<li>Koop een domeinnaam</li>
<li>Heb een computer die aan het internet verbonden is (dit kan ook je Synology NAS zijn bijvoorbeeld)</li>
<li>Draai een linux versie als besturingssysteem (dit heb je wanneer je Synology gebruikt) <sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></li>
<li>Installeer Docker (dit is er al wanneer je Synology versie 5.2 of hoger draait)</li>
<li>Zorg dat je bij de terminal kan met voldoende rechten (SSH met een applicatie als Putty wanneer je de Synology gebruikt - zet wel even SSH aan bij je Synology)</li>
<li>Stuur op je router de juiste poorten door naar je computer (80 en 443) of Synology (poorten aangepast forwarden - Synology hangt daar namelijk hardcoded zijn eigen webserver aan vast)</li>
<li>Plak de bovenstaande gepersonaliseerde code, en gaan met die banaan.</li>
</ol>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Nou ja, het kan denk ik ook met Windows, alleen plak je de code dan in de command prompt. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Kan dus ook via de command prompt in Windows <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Google Tag Manager: 1 tag to rule them all]]></title><description><![CDATA[Google Tag manager. Met 1 container (ID), toch cross domain tracken. Het kan, maar vergt wel wat vinger gymnastiek. Importeer de json en start extra snel met cross domain tracking.]]></description><link>https://www.timhuesken.nl/google-tag-manager-1-tag-to-rule-them-all/</link><guid isPermaLink="false">62a224247b4523000101d787</guid><category><![CDATA[Analytics]]></category><category><![CDATA[javascript]]></category><category><![CDATA[tagmanager]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 13 Sep 2018 15:01:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2018/09/One-Ring-to-Rule-them-All.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2018/09/One-Ring-to-Rule-them-All.jpg" alt="Google Tag Manager: 1 tag to rule them all"><p>Google Tag Manager. Leuk concept, handig, en best makkelijk. Geen gedoe met allerlei snippets, alleen gedoe met 1 snippet in je website sjablonen/pagina&apos;s.</p>
<h2 id="snippets">Snippets</h2>
<p>De rest van de snippets worden allemaal aangeboden in een prettigere gebruikersinterface, zodat iemand die een beetje weet wat ie doet, maar geen programmeer-ervaring heeft (javascript met name), gewoon allerlei dingen kan instellen en aanpassen. Handig!</p>
<p>Vooral handig wanneer je gewoon een centraal overzicht wilt hebben van alle snippets en troep die je op je website laat draaien. Gewoon wat regeltjes (triggers in Tag manager) die bepalen welke tag wanneer wordt afgevuurd. En dat is echt prima wanneer je gewoon simpele tags hebt die per domein dienen te worden ingezet, of per soort pagina&apos;s, etc.</p>
<h2 id="crossdomaintagmanager">Cross domain &amp;&amp; Tag Manager</h2>
<p>Zodra je cross domain gaat, moet je wel nog even ervoor zorgen dat relevante informatie netjes wordt overgedragen. Bijvoorbeeld bij Analytics. En dan wordt het toch al snel een stuk minder overzichtelijker in Tag manager t.o.v. gewoon de snippets in je template(s) kwakken, of gewoon per .html pagina een snippet defini&#xEB;ren. Die nauwkeurige, verfijnde manier van controle hebben over elke pagina op basis van gewoon een stukje javascript, die ging ik al snel missen.</p>
<p>Vooral omdat de conventies binnen Tag manager weer net even iets anders werken dan een snippet.</p>
<h2 id="tagmanagermogelijkheden">Tag Manager mogelijkheden</h2>
<p>In de loop van de tijd heeft Google de Tag manager van allerlei updates voorzien, waarbij je tegenwoordig ook echt gewoon Custom HTML en javascript macro&apos;s en functies kunt inzetten en laten gebeuren op basis van situaties/triggers.</p>
<blockquote>
<p>Het mooiste daar van is dat je de variabelen/functies die je hebt gemaakt, kunt uitwisselen binnen andere functies of tags.</p>
</blockquote>
<p>Elke variabele is binnen een andere variabele aan te roepen middels <code>{{variabele x}}</code>. Je kunt dan ook gewoon lekker javascripten er mee. Bijvoorbeeld:</p>
<pre><code class="language-javascript">    var a, b = {{variabele x}}.split(&apos;,&apos;),c,x,y;
        for  (a = 0; a &lt; b.length; a++) {
            if (x === y) { 
                c = y;
                break;
            }
        }
</code></pre>
<p>en <code>{{variabele x}}</code> kan gewoon ook weer een functie met berekening zijn of wat dan ook. Erg leuk.</p>
<h2 id="crossdomainanalyticsjs">Cross domain Analytics.js</h2>
<p>In mijn geval had ik bijvoorbeeld een domein A en een domein B, waarbij op domein A een iframe werd ingeladen waarin domein B werd aangeroepen om een formulier te presenteren en af te handelen. Helaas is dit dus crossdomain.</p>
<p>Wanneer je bijvoorbeeld met Google Analytics werkt, zal deze de aanroep van het iframe zien als een nieuwe sessie. Zelfs een nieuwe user-id voor aanmaken. Hierdoor krijg je dus statistieken die niet meer aan elkaar te knopen zijn. Het lijkt in dit scenario gewoon alsof de gebruiker die het formulier invulde, een compleet andere gebruiker is, die zomaar even landt op je formulierpagina, en weer vertrekt.</p>
<p>En nee, wanneer het op iframe&apos;s aankomt, kun je niet zomaar even de code van de iframe pagina manipuleren vanaf het hoofddomein (en daarmee bijvoorbeeld het user-id even corrigeren of overzetten) en vice versa. Dat is om veiligheidsredenen gewoon geblokkeerd. En maar goed ook.</p>
<h2 id="postmessage">postMessage</h2>
<p>Eugh, dacht ik destijds. Mijn voorganger had dit verder ook nooit afgemaakt of juist ge&#xEF;mplementeerd. Daar was ik aan begonnen en had uiteindelijk toch gekozen voor het gebruik van de <code>postMessage API</code>, om analytics data van domein A naar domein B te communiceren.</p>
<h3 id="linkerenurldecoreren">Linker en url-decoreren</h3>
<p>Google heeft ook mogelijkheden met de <code>Linker plugin</code>, of gewoon de <code>Linker</code>-functionaliteit in te zetten, maar dan krijg je dus van die lelijke links zoals <code>https://www.domeinb.com/?_ga=1.213353566.4232634566</code></p>
<p>Dit is overigens wel de enige manier wanneer je geen controle hebt over domein b, wanneer je als domein a beheerder die informatie wil overdragen.</p>
<p>Wanneer je deze manier toepast bij cross domain iframe welke je op je hoofd domain inlaadt, zal het frame via een onload=maakErEenAnalyticsUrlVan(); moeten worden ingezet. Dan krijg je afhankelijk van de snelheid van de verbinding en webservers even een flikkerend iframe dat opnieuw wordt ingeladen. En je loopt het risico dat de pageview in het iframe al is getriggerd. 3 keer bah en onhandig.</p>
<h3 id="waaromtochpostmessage">Waarom toch postMessage?</h3>
<p>postMessage is hierin beter, maar vergt wel even wat afstemming binnen je code die je zelf beheert, en eventueel de code van het andere domein, welke je wellicht niet beheerd. Heb je wel toegang tot de code op het andere domein (in je iframe bijvoorbeeld), ben je sneller klaar. Anders moet je met de beheerder van het andere domein afstemmen wat de communicatie zal zijn via postMessage tussen beide domeinen.</p>
<h3 id="timing">Timing</h3>
<p>Zo kun je ervoor kiezen om vanuit het hoofddomein een bericht te sturen zodra de user-id in Analytics is geregistreerd. Dit bericht stuur je dan naar je iframe domein b. Maar... wat nu als het hoofddomein al klaar is en het bericht wil gaan sturen, maar het iframe domein is nog niet ingeladen en is dus nog niet aan het luisteren via de Eventlistener? Dan stuur je de user-id zomaar de ruimte in, want echte controle zit er niet zomaar even in. Iedere pagina kan berichten sturen via postMessage naar welke locatie dan ook. Wat je beter kunt doen, is het laatst ingeladen domein degene laten zijn die het verzoek doet. Dit child domain, in de iframe is degene die aan de parent domain vraagt wat de Google user-id is, waarna het child domain gaat zitten luisteren of er iets terug komt. Het parent domain krijgt dat het verzoek als luisterende binnen en geeft antwoord. Dit is de beste manier om de timings-uitdagingen op te lossen.</p>
<h3 id="geldigeafzender">Geldige afzender</h3>
<p>Het is aan de ontvanger om te luisteren en te bepalen of het een geldig soort bericht is, en of het van een vertrouwd domein komt. Ja, je kunt zeggen als luisteraar: &apos;*&apos;, maakt mij niet uit waar het vandaan komt, en er klaar mee zijn, maar dat brengt potenti&#xEB;le veiligheidsrisico&apos;s met zich mee (zeker met de huidige AVG wil je daar voorzichtig mee zijn). Dus dit moet je ook weer aangeven in je Eventlistener. Geef daar jouw domeinen aan die mogen verzenden. Binnen Google Tag manager kan je dit doen met een Aangepaste HTML-tag waarbij je met variabele topOrigin een set aan domeinnamen kan meegeven, die geaccepteerd worden als afzender. Onderstaande code plaats je dus op je externe domein welke in de iframe wordt ingeladen:</p>
<pre><code class="language-javascript">&lt;script&gt;
// Add the expected origin domain inside gtm topOrigin variable
// Must use the following format http://example.com
var topOrigin, topOrigins = {{gtm topOrigin}}.split(&apos;,&apos;);
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  // Check for the whitelist when cid gets returned
  var found = false;
  for (var i = 0; i &lt; topOrigins.length; i++) {
    if (topOrigins[i] == origin) {
	topOrigin = topOrigins[i];
      found = true;
      break;
    }
  }
  if (topOrigin != &apos;*&apos; &amp;&amp; topOrigin != event.origin) {
    return;
  }
  try {
    var data = JSON.parse(event.data);
  } catch (e) {
    // SyntaxError or JSON is undefined.
    return;
  }
  if (data.cid) {
    sendHit(data.cid);
  }
}

if (window.addEventListener) {
  window.addEventListener(&apos;message&apos;, xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent(&apos;onmessage&apos;, xDomainHandler);
}

var alreadySent = false;
function sendHit(cid) {
  if (alreadySent) return;
  alreadySent = true;
  // If cid exists, it will overwrite any existing cookies.
  var params = {};
  if (cid) params[&apos;clientId&apos;] = cid;
  
  dataLayer.push({&apos;event&apos;:&apos;trackPage&apos;,&apos;cid&apos;: cid});
}
if (!window.postMessage) {
  // If no postMessage Support.
  sendHit();
} else {
  // Resolve current topOrigin for initial message request
  var a=document.createElement(&apos;a&apos;);
  a.href=document.referrer;
  topOrigin = a.href;
  // Tell top that we are ready.
  top.postMessage(&apos;send_client_id&apos;, topOrigin);
  // Set a timeout in case top doesn&apos;t respond.
  setTimeout(sendHit, 100);
}
&lt;/script&gt;
</code></pre>
<p>Op het hoofd domein (parent domain), welke de iframe inlaadt, zorg je ervoor dat de volgende aangepaste HTML-tag wordt ingeladen:</p>
<pre><code class="language-javascript">&lt;script&gt;
// Edit the gtm allowedOrigins variable and add your domains to the whitelist
var allowedOrigins = {{gtm allowedOrigins}}.split(&apos;,&apos;);
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  
// Check for the whitelist.
  var found = false;
  for (var i = 0; i &lt; allowedOrigins.length; i++) {
    if (allowedOrigins[i] == origin) {

      found = true;
      break;
    }
  }
  if (!found) return;
  if (event.data != &apos;send_client_id&apos;) return;

// Get the clientId and send the message.
  var tracker = ga.getAll()[0];
    tracker.get(&apos;clientId&apos;);
    var data = {cid: tracker.get(&apos;clientId&apos;)};
    event.source.postMessage(JSON.stringify(data), origin);
}
if (window.addEventListener) {
  window.addEventListener(&apos;message&apos;, xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent(&apos;onmessage&apos;, xDomainHandler);
}
&lt;/script&gt;
</code></pre>
<p>De variabele allowedorigins vul je dan weer met je child domeinen als geaccepteerde regexwaarde.</p>
<p>Om een lang verhaal kort te maken; Gebruik dit <a href="https://www.timhuesken.nl/content/images/2018/09/GTM-MLWW28_v19.json">json-bestand</a> en importeer deze container met instellingen in je werkruimte van Tag manager om een blanco startpakket te gebruiken. Op basis hiervan en wat tweaks voor jezelf (denk aan anonimizeIP = true in je analytics tag instellingen i.v.m. de AVG), en je kunt crossdomain gaan tracken. Het enige wat je moet doen, is op beide domeinen dezelfde GTM-container inladen en de juiste triggers gebruiken bij de juiste domeinen.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adwords Editor Account naam aanpassen]]></title><description><![CDATA[Mijn adwords account heeft ineens een andere naam?! Die van de externe partij die even een quickscan voor ons zou doen. Hoe je de naam kan terugzetten is niet zo eenvoudig helaas. Er is wel een manier gelukkig.]]></description><link>https://www.timhuesken.nl/adwords-editor-account-naam-aanpassen/</link><guid isPermaLink="false">62a224247b4523000101d786</guid><category><![CDATA[Adwords]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Mon, 01 May 2017 08:27:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/maxresdefault.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/maxresdefault.jpg" alt="Adwords Editor Account naam aanpassen"><p>Altijd een leuke. Geef je toegang tot je Adwords account aan een derde partij die een MCC (&apos;My Client Center&apos;) account heeft (tegenwoordig &apos;Adwords manager accounts&apos;), zie je bij de volgende account update in Adwords Editor hun naam als je Adwords account naam verschijnen.</p>
<p>Lekker irritant. Beetje ongewenste marketing van hun zijde. Ik hoef niet telkens hun bedrijfsnaam/codenaam voor ons Adwords account te zien. Al helemaal niet wanneer ze alleen maar even een quickscan hebben gedaan voor ons. Kom op zeg.</p>
<h2 id="vervelend">Vervelend...</h2>
<p>dat dit fenomeen slecht gedocumenteerd is op het web. Het zal vast wel ergens vermeld staan hoe dit gedaan kan worden, maar ik kon er maar moeilijk iets over vinden.</p>
<h2 id="oorzaak">Oorzaak</h2>
<p>Nu kun je wanneer je toegang krijgt tot een Adwords account in de rol van Adwords manager (MCC) de naam van het account aangeven voor binnen je MCC. Zo kun je onderscheid maken tussen de verschillende accounts die je beheert. Wat er niet bij vermeld wordt is dat deze benaming de huidige accountnaam overschrijft, ook bij de eigenaar(s).</p>
<p>Wanneer je dus op het originele account inlogt, kan het zijn dat je zomaar die naamgeving ziet staan. En het is al helemaal overduidelijk in de Adwords Editor.</p>
<h2 id="nietbedoeld">Niet bedoeld</h2>
<p>Ik denk overigens dat deze derde partij die heeft gekeken naar het account, zich hier niet van bewust is. Vervelende is naast dit, dat je het pas weer kunt veranderen als je het account aan een MCC hangt. In het originele account kan dit niet..., niet via Adwords Editor, en niet via de WebUI.</p>
<h2 id="oplossing">Oplossing</h2>
<p>Dus ja, de oplossing is een MCC account regelen (als je die nog niet hebt), en dan de koppeling maken en de naam opgeven die het was of die je wilt hebben. Daarna ontkoppelen en voil&#xE0;. Opgelost.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Analytics spam neemt weer toe, nu via taal]]></title><description><![CDATA[Holy cow, ineens 1000 pageviews in een paar uur tijd?! Eerste reactie: Vet! Twee seconden later: Shit. Zal wel weer spam zijn. Jep.]]></description><link>https://www.timhuesken.nl/analytics-spam-neemt-weer-toe-nu-via-taal/</link><guid isPermaLink="false">62a224247b4523000101d784</guid><category><![CDATA[Analytics]]></category><category><![CDATA[SEO]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Tue, 06 Dec 2016 20:22:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/What-is-email-spam_1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/What-is-email-spam_1.jpg" alt="Analytics spam neemt weer toe, nu via taal"><p>De laatste week is het weer raak bij Analytics. Al jaren hebben de meeste site-beheerders last van spam bezoeken die de statistieken negatief be&#xEF;nvloeden, en soms zelfs compleet om zeep helpen. Ik schreef hier al eerder over, de zogenaamde &apos;ghost spam&apos; zoals dit bekend staat in de online-wereld. Nu is er echter een andere vorm bijgekomen die langs alle filters lijkt te komen, die de meeste Analytics beheerders nu onderhand wel hebben ingesteld. Spam via taal instellingen.</p>
<h2 id="hoewerkthet">Hoe werkt het?</h2>
<p>Het werkt als volgt; Spam via bots komt binnen op analytics, met als user-agent instelling voor taal iets in de trant van: <code>Secret.&#x262;oogle.com You are invited! Enter only with this ticket URL. Copy it. Vote for Trump!</code>, en vandaag heb ik er weer een nieuwe bij: <code>o-o-8-o-o.com search shell is much better than google!</code></p>
<h2 id="watdoethet">Wat doet het?</h2>
<p>Deze spam gedraagd zich iets anders dan de ghost spam van de afgelopen jaren, wat niet meer dan een 1 pagina per sessie bezoek met een duur van 0:00 minuten was. Deze spam bezoek echter ook echt pagina&apos;s, 4,14 per sessie, en hangt zo&apos;n 1:30 gemiddeld op je site rond. Irritant!</p>
<h2 id="oplossingenteover">Oplossingen te over</h2>
<p>Gelukkig zijn er filters en properties die je kunt toepassen. In mijn geval is het vrij eenvoudig te weren tot nu toe. Je maakt gewoon een filter aan met de instellingen:</p>
<blockquote>
<p>taal-instellingen, uitsluiten, filterpatroon <code>oogle.*</code>. Even toepassen op al je properties als je er meer hebt, en je bent er vanaf.</p>
</blockquote>
<p>Deze specifieke instelling werkt vooral voor waar ik mee te maken kreeg. Beiden gebruiken &#xE9;&#xE9;n overeenkomstig woord/letter combinatie, <code>oogle</code> in de taal-instellingen.</p>
<h3 id="waaromoogle">Waarom <code>oogle</code>?</h3>
<p>Ik zag dat de eerste spammer waar ik te maken mee kreeg, <code>&#x262;oogle</code> schreef, waarbij de eerste <code>&#x262;</code> een ander teken is dan de tweede <code>g</code>. Sneaky!</p>
<h3 id="nogeenregexpatroonwatjekuntinzetten">Nog een RegEx patroon wat je kunt inzetten</h3>
<p>Deze werkt iets anders dan degene die ik tot nu toe heb gebruikt en ziet er als volgt uit:</p>
<pre><code class="language-regexp">    .{15,}|\s[^\s]*\s|\.|,|\!|\/
</code></pre>
<p>Deze filter focust zich op de speciale tekens die er inzitten bij de language spammers. Normale bezoekers hebben de land-codes welke meestal bestaan uit 2 letters, een streep en weer 2 letters. Dus nl-NL bijvoorbeeld. Het bovenstaande patroon filtert dus alles met een punt, komma, uitroepteken en een /-teken. Daarnaast ook nog alles qua taal-instelling dat groter is dan 15 tekens. Op zich redelijk netjes, totdat de spammers ook daar weer mee aan de slag gaan. Te denken valt aan een short-URL.</p>
<h2 id="tijdelijkesegmenten">Tijdelijke segmenten</h2>
<p>Maar goed, dan heb je nog te maken met de achtergelaten vervuiling. Dit is op te lossen door een segment te maken waarbij je dezelfde instelling hanteert en de taal-spammers eruit filtert. Dit kun je tijdelijk inzetten om je eventuele rapportages te corrigeren. Bij mij scheelde het nogal in bezoekersduur en pagina&apos;s per sessie.</p>
<p>Deze spam is namelijk nogal aanwezig qua volume. Tevens worden er ook verwijzers gebruikt welke legitieme sites zijn, waaronder bijvoorbeeld reddit.com en lifehacker.com afgelopen week. Tja, die wil je misschien als verwijzer niet blokkeren.</p>
<h2 id="omgedraaidefilter">Omgedraaide filter</h2>
<p>Ik zit er nog aan te denken om het voorbeeld van de taalfilter hierboven even om te draaien. Ik kan natuurlijk bezig blijven met het uitsluiten van rare taal-instellingen, maar waarom niet gewoon een taalfilter maken waarbij ik alles uitsluit wat niet overeenkomt met de bekende taal-instellingen. Dat is op zich prima te bouwen. De reden dat dit op zich zou moeten werken, is omdat het de spammers toch echt om aandacht gaat. En wanneer ze alleen doorkomen bij ons in de statistieken met een geldige taal-setting, dan is het doel niet behaald.</p>
<p>Ik zal deze eens gaan opzetten en even test-draaien. Daarna zal ik deze hier melden en waarschijnlijk is er al iemand die deze heeft gemaakt inmiddels en beschikbaar heeft gemaakt binnen de templates voor Google Analytics.</p>
<p><mark><strong>UPDATE 1:</strong></mark> Ben nu een totaalrapport met alle language settings aan het exporteren, daar er geen simpel overzicht te verkrijgen was met alle valide taal instellingen voor Google Analytics, dus dan maar zelf 1 maken. Die zet ik dan dadelijk om naar een RegEx filter patroon en zal zo dadelijk een filter aanmaken waarbij ik deze lijst laat opnemen. Voor nu is dat de meest toekomst-veilige ofwel future-proof oplossing.**</p>
<h3 id="filtervooropnemenvanalleengeldigetaalinstellingeninanalytics">Filter voor opnemen van alleen geldige taalinstellingen in Analytics</h3>
<p><strong><mark>UPDATE 2:</mark></strong> Ik heb een filter gemaakt met een patroon wat iets ruimer is dan de strikte iso landen code lijsten waar Google gebruik van maakt (ja, er zijn er meer).</p>
<ul>
<li><a href="http://www.loc.gov/standards/iso639-2/php/code_list.php">ISO 639 specifies 2-letter and 3-letter codes for languages.</a></li>
<li><a href="http://www.unc.edu/~rowlett/units/codes/country.htm">ISO 3166 specifies 2-letter and 3-letter codes for countries, as well as 3-digit codes.</a></li>
</ul>
<p>Gebruik 1 van onderstaande filterpatronen op basis van je voorkeur. De eerste gebruik ik zelf en is wat ruimer dan per se nodig. Ik doe dit omdat ik tussen de data ook taalinstellingen als <code>en-029</code> e.d. tegenkwam. Dit zijn dan wel geen offici&#xEB;le taalcodes, maar ze zitten mij ook niet in de weg. Die kan ik later altijd nog wegfilteren.</p>
<p>Het ruimere patroon:</p>
<pre><code class="language-regexp">    ^[a-zA-Z0-9_]{1,2}(\-|\_)[a-zA-Z0-9_]{2,3}$|^[a-zA-Z0-9_]{1,2}$|\(not set\)
</code></pre>
<p>En het striktere patroon voor mensen die het officieel willen hebben:</p>
<pre><code class="language-regexp">    ^[a-z]{2}(\-|\_)[A-Z]{2}$
</code></pre>
<p>Dus:</p>
<ul>
<li>Gewoon naar je filters gaan (zorgen dat deze op de juiste properties wordt geplaatst wanneer je er meer dan 1 hebt)</li>
<li>Kiezen voor aangepast of custom</li>
<li>Opnemen of include kiezen</li>
<li>Veld of field <code>taalinstellingen</code> of <code>language settings</code></li>
<li>Filterpatroon zoals hierboven invoeren en opslaan die hap.</li>
</ul>
<h2 id="totslot">Tot slot</h2>
<p>Deze filter bespaard je in de toekomst het nodige werk van het bijwerken van een uitsluiten/exclude filter, en zal ook prima blijven werken, daar het de spammers toch om aandacht gaat. En die aandacht voor een URL of iets anders gaat ze niet lukken met maximaal 5 tekens, gescheiden door een <code>-</code> of <code>_</code>. Dus niet meer interessant voor hen.</p>
<blockquote>
<p>Hiermee heb je toekomstige taalinstellingen-spammers dus ook weggefilterd!</p>
</blockquote>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ongeduldige website bezoekers en formulieren]]></title><description><![CDATA[Klik klik klik op de verzendknop. En bedankt... weer 3 submissions... Doe es effe rustig joh. Of ben jij zo'n gebruiker die op het internet alles dubbelklikt zoals je in windows doet?]]></description><link>https://www.timhuesken.nl/ongeduldige-website-bezoekers-en-formulieren/</link><guid isPermaLink="false">62a224247b4523000101d783</guid><category><![CDATA[parsley.js]]></category><category><![CDATA[recaptcha]]></category><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Mon, 10 Oct 2016 20:56:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/waiting-7_1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/waiting-7_1.jpg" alt="Ongeduldige website bezoekers en formulieren"><p>Ik ben al een tijdje op mijn gemak bezig met het moderniseren van de webformulieren voor de websites van mijn werk, en heb gekozen voor een separate client-side validatie techniek, aangevuld met separate server-side validatie nadien. Dit om ervoor te zorgen dat ik zo min mogelijk a-synchrone call-back scenario&apos;s hoef af te dekken. Niet dat ik AJAX schuw, maar gewoon omdat het voor nu gewoon sneller opgeleverd kan worden en het ook even niet complexer hoeft te worden dan nodig is. Dit laatste vooral vanwege het gebruik van 3rd party oplossingen waar een content-editor gewoon in moet kunnen blijven werken en mij niet bij elke inhoudelijke aanpassing nodig moet hebben. Ockhams scheermes als het even kan!</p>
<h2 id="setupvoorclientsidevalidatielibrary">Setup voor client-side validatie library</h2>
<p>Ik heb gekozen voor het gebruik van <a href="http://parsleyjs.org">parsley.js</a>, daar het precies de mogelijkheden biedt die ik zocht.</p>
<p>Ik heb dit verrijkt met de Bootstrap opmaak van de eigen site en alles draaide prima. Totdat ik merkte dat al vrij snel meerdere formulier registraties direct achter elkaar binnen begonnen te komen. En zo heel af en toe kreeg ik er 20+ binnen, met een uitschieter naar ~100...</p>
<h2 id="spam">Spam?</h2>
<p>Oeps! Na alles na gelopen te hebben, kwam ik erachter dat er op zich niets mis was met mijn implementatie. De client-side verliep prima. Parsley JS deed zijn ding en er waren niet echt scenario&apos;s te bedenken waar mijn initialisatie en validatie regels voor Parsley fout zouden verlopen (gesloten proces waar de gebruiker doorheen loopt), en ook met testen kwam ik niets tegen waar dit mis zou gaan.</p>
<h2 id="nietstevinden">Niets te vinden?</h2>
<p>Toch maar eens geprobeerd mijzelf nog eens goed in de gebruikers te verplaatsen. 100 formulier inzendingen in 5 minuten... Trage verbinding? Wellicht. Zitten er verschillen in de ingevoerde data door de gebruiker? Niet echt, een beoordelingscijfertje aangepast, verder niet. Dus in ieder geval is de gebruiker &apos;terug gegaan&apos; na inzending. Kan gebeuren. Maar zoveel dubbelen is niet juist. Hoe zou een gebruiker op zo&apos;n formulier klikken?</p>
<p>En toen had ik het ineens. Klikken... 2 keer op verzenden klikken voordat het formulier doorgaat naar de eindpagina? De befaamde dubbelklik? Yep...</p>
<p>Dus ook als een verbinding trager lijkt te zijn? 5 keer? Yep... Bijzonder dat de persoon met 100 inzendingen dan ook echt heeft zitten klikken als een malle, maar goed, begrijpelijk.</p>
<h2 id="oplossendiehap">Oplossen die hap</h2>
<p>Okay! Ik heb iets waar ik mee kan werken. Om bovenstaand te voorkomen heb ik een simpele gif in een div met hoge z-index ingezet die bij de verwerking van de Parsley code wordt getoond, en natuurlijk verdwijnt zodra de eindpagina wordt getoond.</p>
<pre><code class="language-javascript">    $(&quot;form&quot;).on(&quot;submit&quot;, function () {
        //Laat het loading scherm zien 
        $(&quot;#loading&quot;).css(&quot;display&quot;, &quot;block&quot;);
        //Voer de rest van de validatie code uit...
</code></pre>
<p>Daarnaast moet je dan ook nog het laadscherm weghalen wanneer er toch iets niet goed blijkt te zijn, ofwel wanneer Parsley terugkomt met validatie-fouten:</p>
<pre><code class="language-javascript">    // Wanneer er een fout in het document wordt gevonden bij inzenden, dient het loading scherm weer weggehaald te worden
      window.Parsley.on(&apos;field:error&apos;, function() { 
        $(&quot;#loading&quot;).css(&quot;display&quot;, &quot;none&quot;);
      });
</code></pre>
<p>En daarmee is de cirkel rond. Server-side validatie loopt hier eigenlijk altijd wel door, zolang er niemand met de client-side resultaten gaat lopen rommelen (maar goed, dan nog komt die netjes terug als dit wel zo zou zijn), reCAPTCHA van Google houdt de bots goed buiten de deur, en dubbelklikkers en trage verbindingen zijn nu geen obstakel meer.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Webformulieren en een datumprikker]]></title><description><![CDATA[Datums en formulieren. Dat is altijd een gedoe, althans meestal. Het ziet er vaak niet handig uit en werkt niet goed op een mobieltje. Er zijn oplossingen!]]></description><link>https://www.timhuesken.nl/webformulieren-en-een-datumprikker/</link><guid isPermaLink="false">62a224247b4523000101d77e</guid><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Mon, 12 Sep 2016 19:57:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/Calendar-Image-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/Calendar-Image-1.jpg" alt="Webformulieren en een datumprikker"><p>Ben de laatste tijd bezig met Javascript. Meer dan ik had gehoopt. Ik vind het heerlijk om zelf met Javascript te knutselen. Tegelijkertijd probeer ik wel te waken om niet overal te pas en te onpas JS in te zetten, als het ook anders kan.</p>
<p>Ik gebruik graag beschikbaar gestelde snippets of libraries, die ik dan aanpas op mijn wensen. Zo wilde ik laatst een datumprikker plaatsen in een formulier, en dan kom je al snel op een Javascript uit. Helemaal wanneer je beperkte invloed hebt op de manier waarop het formulier wordt opgebouwd (3e partij en geen toegang tot hun source).</p>
<p>Nou ja, je zou het gehele formulier kunnen herschrijven met behulp van XLST, en er dan een HTML5 verhaal van maken (wat zeker nog op mijn to-do lijst staat, met Bootstrap smaakje), maar dat is een project an sich. Tevens zijn de HTML5 input features niet geschikt voor alle devices en browsers op het moment, dus je zit toch al snel met nog wat Javascript te kijken.</p>
<p>In dit geval heb ik daar nu de tijd niet voor en moet het snel opgeleverd worden.</p>
<h2 id="zoeken">Zoeken</h2>
<p>Dus op zoek naar dat script. Tijdens mijn zoektocht kom je dan wel een aantal scripts tegen die behoorlijk wat mogelijkheden lijken te bieden, maar dan toch weer her en der, bij implementatie, in de soep draaien (uitsluiten van datums en tijdsmomenten, mogelijkheden tot opmaak gelijk trekken met de huisstijl).</p>
<p>Dan kun je drie dingen doen;</p>
<ul>
<li>Troubleshooten en uiteindelijk bugfixen in het hoofdscript (waar jij niet aan de basis hebt gestaan dus aardig wat extra tijd kwijt bent om het te doorgronden)</li>
<li>Zelf van de grond af aan opbouwen (want alles naar eigen wens)</li>
<li>Weggooien en op zoek naar een alternatief</li>
</ul>
<p>Ik heb eerst getroubleshoot, maar al snel de bijl begraven en ik ben na een paar snelle simpele fixes die niet het gewenste resultaat hadden, verder gaan zoeken. Zelf bouwen was al snel vergeten door mij, want tijd en onderhoud. Zeker als je nog op tijdelijk contract staat is dit niet de keuze die je wilt maken.</p>
<p>Uiteindelijk ben ik bij <a href="http://amsul.ca/pickadate.js/">pickadate.js</a> van <a href="https://github.com/amsul">Amsul</a> terecht gekomen. Belangrijkste criteria kon aan voldaan worden;</p>
<ol>
<li>hoog configureerbaar - Kantooruren + speciale dagen met afwijkende tijden.</li>
<li>in de basis responsive design.</li>
<li>goed gedocumenteerd en dus snel(ler) manipuleerbare source.</li>
</ol>
<h2 id="topmaartoch">Top, maar toch...</h2>
<p>Alsnog heb ik een volledig custom aanroep erboven op geschreven om bijvoorbeeld een extra knop te maken die de eerstvolgende speciale datum invult zodra deze aangeklikt wordt. Daarna was het van belang om het beheerbaar te houden en heb ik een setje datums aan het begin van het bestand in gegeven, waarna de een functie zelf gaat evalueren (loop met splice en sort door een array heen) of de datum nog wel geldig is. Dit voorkomt dat ik de bestanden maandelijks mag gaan updaten. Ik heb wel wat beters te doen. Zeker als de datums meestal voor een heel jaar bekend zijn...</p>
<p>Onderaan het blog zal ik de custom initialisatie code plaatsen zodat anderen er eventueel gebruik van kunnen maken.</p>
<p>Tevens wilde ik een eenvoudige knop bovenaan in de datumkiezer zetten. Er staat namelijk standaard &apos;Sluiten&apos;, &apos;Wissen&apos; en &apos;Vandaag&apos; beschikbaar. Handige functionaliteit. Ik wilde gewoon de eerstvolgende door mij aangegeven speciale datum selecteert.</p>
<p>Op zich vrij eenvoudig, omdat ik een Array heb die ik splice op basis van het moment waarop een gebruiker deze bekijkt. Dus ik vergelijk op het moment van het openen van de datumkiezer het huidige moment (<code>new Date();</code>) met de speciale datums en splice deze uit de array en sort deze daarna van laag naar hoog. Dit betekent dat de dichtst bijzijnde datum ten opzichte van vandaag altijd op index 0 in de array zit.</p>
<p>Dit kan ik vervolgens gebruiken om gewoon de functionaliteit van de &apos;Vandaag&apos; knop te kopi&#xEB;ren en in de source van pickadate.js toevoegen.</p>
<p>Daarna wilde ik dat deze knoppen boven het kalendergedeelte kwamen te staan. Onderin stonden ze zo verstopt.</p>
<p>Ook de source code aangepast en het gedeelte dat de html rendert aangepast en verplaatst.</p>
<p>Erg leuk maar voor je het weet zit je echt knie-diep in custom code, met risico&apos;s dat het weer moet bijgehouden worden bij updates etc.</p>
<p>Dat was voor mij ook het moment om te stoppen met aanpassingen. Alles natuurlijk goed gedocumenteerd met comments, maar ja... we weten allemaal hoe nuttig die soms zijn...</p>
<p>Ik maak tevens gebruik van jQuery om de DOM her en der te manipuleren en te tweaken, maar ook om pickadate.js te binden bijvoorbeeld. Deze jQuery selectors kun je natuurlijk vervangen met <code>document.getElementById(&quot;id&quot;).pickadate();</code> bijvoorbeeld. Wel eerst even zorgen dat jQuery wordt ingeladen op je webpagina natuurlijk.</p>
<p>Een demo:</p>
<p data-height="450" data-theme-id="0" data-slug-hash="rmwegm" data-default-tab="html,result" data-user="TimHuesken" data-embed-version="2" data-pen-title="pickadate.js example" class="codepen">See the Pen <a href="https://codepen.io/TimHuesken/pen/rmwegm/">pickadate.js example</a> by Tim Huesken (<a href="http://codepen.io/TimHuesken">@TimHuesken</a>) on <a href="http://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>Zoals beloofd (lap code):</p>
<pre><code class="language-javascript">    /*Custom aanroep van pickadate.js door Tim Huesken - tim.huesken[at]outlook.com*/

    /*-------------------------------------------------------------------*/
    /*Alle speciale datums.*/
    /*Format waarin je een datum opvoert: new Date(Jaar, Maandnummer-1,Dag,Uur,Minuut,Seconde,Milliseconde).getTime(),*/
    /*Oude datums kun je laten staan, die worden netjes eruit gefilterd*/
    var zondagen = [ new Date( 2016, 1, 21, 0, 0, 0, 0 ).getTime(), new Date( 2016, 3, 3, 0, 0, 0, 0 ).getTime(), new Date( 2016, 4, 16, 0, 0, 0, 0 ).getTime(),
                    new Date( 2016, 5, 26, 0, 0, 0, 0 ).getTime(), new Date( 2016, 8, 11, 0, 0, 0, 0 ).getTime(), new Date( 2016, 10, 6, 0, 0, 0, 0 ).getTime(),
                    new Date( 2016, 11, 18, 0, 0, 0, 0 ) ];
    /*------------------------------------------------------------------*/

    //Kies hier de tekst van de knop en de popup bij mouseover speciale datums.
    var textButton = &quot;Speciale Zondag&quot;;
    var zondag = &apos;&apos;;
    var format = &apos;yyyy-dd-mm&apos;;

    /*Hiermee wordt de Speciale zondag button gedefinieerd.*/
    /*Functionaliteiten en stijl kun je hier manipuleren. Haal bijvoorbeeld de regel weg en vervang met: var appendButton = &apos;&apos;; om de button in zijn geheel weg te halen.*/
    var appendButton = &apos;&lt;button id=&quot;zondag&quot; class=&quot;picker__button--zondag&quot; type=&quot;button&quot;&gt;&apos; + textButton + &apos;&lt;/button&gt;&apos;;

    //Helper functie voor het toevoegen van de juiste data-pick waarde voor de Speciale zondag button. Hier hoef je niets aan te wijzigen.
    function dataPickZondag(){
        for ( var i = zondagen.length -1; i &gt;= 0; i-- ) {
            if ( new Date().getTime() &gt;= zondagen[ i ] )  {
                zondagen.splice( i, 1 );
            }
        }
        for ( var j = 0; j &lt; zondagen.length -1; j++ ) {
          if ( $( &apos;[data-pick=&apos; + zondagen[ j ] + &apos;]&apos; ) !== null ) {
                  $( &apos;[data-pick=&apos; + zondagen[ j ] + &apos;]&apos; ).addClass( &apos;pickadate--zondag&apos; );
                  $( &apos;[data-pick=&apos; + zondagen[ j ] + &apos;]&apos; ).attr( &quot;title&quot; , &quot;Speciale zondag&quot; );
                  $( &apos;[data-pick=&apos; + zondagen[ j ] + &apos;]&apos; ).removeClass( &apos;picker__day--disabled&apos; );
              }
          }
        zondagen.sort();
        if ( $( &apos;[data-pick=&apos; + zondagen[ 0 ] + &apos;]&apos; ) !== null ) {
                    $( &apos;#zondag&apos; ).attr( &quot;data-pick&quot;, zondagen[0] );
            }
            else {
            $( &apos;button&apos; ).remove( &quot;.picker__button--zondag&quot; );    
            }
    }

    /*Hier worden de datumprikker en tijdprikker aangeroepen en opgemaakt. Basis instellingen vind je hier.*/
    var datepicker = $( &quot;input[placeholder=&apos;Datum placeholder&apos;]&quot; ).pickadate({
            firstDay: 1,
            min: new Date(),
            max: +45,
            //formatSubmit: format,
            hiddenName: true,
            //closeOnSelect: false,
            closeOnClear: false,
            disable: [
            true,
            1,2,3,4,5,
            [2016, 1, 21],
            [2016, 3, 3],
            [2016, 3, 27, &apos;inverted&apos;],
            [2016, 4, 5, &apos;inverted&apos;],
            [2016, 4, 16],
            [2016, 5, 26],
            [2016, 8, 11],
            [2016, 10, 6],
            [2016, 11, 18],
            [2016, 11, 25, &apos;inverted&apos;],
            [2016, 11, 26, &apos;inverted&apos;],
            [2017, 1, 1, &apos;inverted&apos;]
            ],
            onRender: function (){
                //format = format + &apos; &apos; + zondag,
                nu = new Date().getTime();
                teLaat = ( new Date().setHours( 0, 0, 0, 0 ) + 56700000 );
                $( &apos;.picker__buttons&apos; ).append( appendButton );
                dataPickZondag();
                $( &apos;.picker__button--clear&apos; ).click( function() {
                    tpicker.clear();
                });
                    if ( nu &gt; teLaat ) {
                        $( &apos;button&apos; ).remove( &quot;.picker__button--today&quot; );
                    }
                }
        }),
        dpicker = datepicker.pickadate( &apos;picker&apos; );

    var timepicker = $( &quot;input[placeholder=&apos;Tijd placeholder&apos;]&quot; ).pickatime({
            format: &apos;HH:i uur&apos;,
            formatSubmit: &apos;HH:i&apos;,
            hiddenName: true,
            min: [9,0],
            max: [16,30],
            disable: [
            [12,0],
            [12,30]
            ]
        }),
        tpicker = timepicker.pickatime(&apos;picker&apos;);

        if (dpicker === undefined) {} else { 
        dpicker.on(&apos;open&apos;, function (event) {
            var nu = new Date().getTime(),
            teLaat = (new Date().setHours(0,0,0,0) + 56700000);
            $(&apos;.picker__button--clear&apos;).click(function(){
                    tpicker.clear();
                });
            if(document.getElementById(&quot;zondag&quot;)){
                dataPickZondag();
            }
            
            if (nu &gt; teLaat) {
                dpicker.set(&apos;min&apos;, 1);
                $( &apos;picker__button--today&apos;).addClass( &apos;picker__day--outfocus picker__day--disabled&apos; );
                $( &apos;[data-pick=&apos; + new Date().setHours( 0, 0, 0, 0 ) + &apos;]&apos; ).removeClass( &apos;picker__day--selected picker__day--highlighted&apos; );
                $( &apos;button&apos; ).remove( &quot;.picker__button--today&quot; );
            }
        });
    }

        if (dpicker === undefined) {} else {
            dpicker.on( &apos;set&apos;, function ( event ) {
                if (( event.select ) &amp;&amp; ( zondagen.indexOf(dpicker.get( &apos;select&apos; ).pick) != -1 )) {
                    //zondag = &apos;JASNO op Zondag&apos;,
                    $( &quot;input[data-name=&apos;Eventtype&apos;]&quot; ).val(&quot;JASNO op Zondag&quot;),
                    $( &quot;input[data-name=&apos;Evenement&apos;]&quot; ).val(&quot;Ja&quot;),
                    tpicker.clear(),
                    tpicker.set(&apos;enable&apos;, true),
                    tpicker.set({
                    interval: 60,
                    min: [11,0],
                    max: [15,0],
                    }),
                    tpicker.on( &apos;open&apos;, function ( remove ) {
                        $( &apos;p&apos; ).remove( &quot;.text-center&quot; );
                        }),
                    setTimeout( tpicker.open, 0 );
                }
                else if (( event.select ) &amp;&amp; ( dpicker.get( &apos;select&apos; ).pick !== new Date().setHours( 0, 0, 0, 0 ) )) {
                    //zondag = &apos;&apos;,
                    $( &quot;input[data-name=&apos;Eventtype&apos;]&quot; ).val(&quot;Bezoek&quot;),
                    $( &quot;input[data-name=&apos;Evenement&apos;]&quot; ).val(&quot;Ja&quot;),
                    tpicker.clear(),
                    tpicker.set( &apos;enable&apos; , true),
                    tpicker.set({
                        interval: 30,
                        min: [9,0],
                        max: [16,30],
                        disable: [
                        {
                            from:[12,0], to:[12,30]
                        }
                        ]
                    }),
                    tpicker.on( &apos;open&apos;, function ( remove ) {
                            $( &apos;p&apos; ).remove( &quot;.text-center&quot; );
                        }),
                    setTimeout( tpicker.open, 0 );
                }
                else if (( event.select ) &amp;&amp; ( dpicker.get( &apos;select&apos; ).pick === new Date().setHours( 0, 0, 0, 0 ) )) {
                    var tijd = (new Date().getTime() + ( 60 * 45 * 1000 )),
                    min = new Date(tijd),
                    uur = min.getHours(),
                    afgerond = ( 30 * ( Math.ceil( min.getMinutes()/30 )));
                    //zondag = &apos;&apos;,
                    $( &quot;input[data-name=&apos;Eventtype&apos;]&quot; ).val(&quot;Bezoek&quot;),
                    $( &quot;input[data-name=&apos;Evenement&apos;]&quot; ).val(&quot;Ja&quot;),
                    tpicker.clear(),
                    tpicker.set( &apos;enable&apos;, true ),
                    tpicker.set({
                        interval: 30,
                        min: [uur,afgerond],
                        max: [16,30],
                        disable: [
                        {
                            from:[12,0], to:[12,30]
                        }
                        ]
                    }),
                    tpicker.on( &apos;open&apos;, function ( remove ) {
                            $( &apos;p&apos; ).remove( &quot;.text-center&quot; );
                        }),
                    setTimeout( tpicker.open, 0 );
                }
            });
        }
</code></pre>
<h2 id="aangepastesource">Aangepaste Source</h2>
<p>Mocht je hier verder gebruik van willen maken, kun je hier de verdere <a href="https://drive.google.com/file/d/0B7kpvX3eZBurbHlPN2c4N044c3c/view?usp=sharing">source files downloaden</a>. Ik verwacht wel van je dat je weet hoe je deze .js en .css bestanden moet aanroepen in je webpagina en dat je weet de input elementen in een form aan te roepen en  succesvol bovenstaande code kunt aanpassen zodat de boel netjes aan de elementen wordt gebonden. Ook neem ik aan dat je de css kunt instellen naar wens. Mochten er onverhoopt toch vragen ontstaan, laat ze hieronder achter en ik zal ze proberen te beantwoorden.</p>
<p>Ook weet ik dat mijn code effici&#xEB;nter en beter kan. Als je iets ziet, mag je het natuurlijk melden. Graag zelfs. :-)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Pagina titels en SERP snippets in 2016 bij Google]]></title><description><![CDATA[Hoe ziet mijn advertentie er nu weer uit? Waarom staan er puntjes achter mijn organische zoekresultaat? Google verandert het nodige aan de SERP snippets en gaat uitgebreidere koppen aanbieden.]]></description><link>https://www.timhuesken.nl/pagina-titels-en-serp-snippets-in-2016-google/</link><guid isPermaLink="false">62a224247b4523000101d782</guid><category><![CDATA[SEO]]></category><category><![CDATA[SERP]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Wed, 07 Sep 2016 09:19:16 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/iris-scan-1_1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/iris-scan-1_1.jpg" alt="Pagina titels en SERP snippets in 2016 bij Google"><p>De laatste maanden zijn er voor degenen die er oog voor hebben en op letten, aardig wat wijzigingen te zien in de SERP (Search Engine Result Page) snippets van Google. Zo experimenteerden ze vorige maand met verschillende kleuren voor de <em><strong>&apos;Adv.&apos;</strong></em> markering bij Adwords advertenties.</p>
<h2 id="adwords">Adwords</h2>
<p>De ene week/dag waren ze nog geel met witte letters, nu regelmatig groen met witte letters. Zie hieronder de verschillen:</p>
<div style="display: flex;">
<div style="position: relative; display: inline-block; margin-right: 5px; width:100%"><a href="https://www.timhuesken.nl/content/images/2016/09/adwords-adv-geel.jpg"><img src="https://www.timhuesken.nl/content/images/2016/09/adwords-adv-geel.jpg" alt="Pagina titels en SERP snippets in 2016 bij Google"></a></div>
<div style="position: relative; display: inline-block; margin-left: 5px; width:100%"><a href="https://www.timhuesken.nl/content/images/2016/09/adwords-adv-groen.jpg"><img src="https://www.timhuesken.nl/content/images/2016/09/adwords-adv-groen.jpg" alt="Pagina titels en SERP snippets in 2016 bij Google"></a></div>
</div>
###Indruk
Mijn indruk is dat dit gemengd wordt ontvangen bij de Online marketeers wereldwijd. Ik moet zeggen dat ik het wel duidelijker vind voor de gebruiker, dat het een advertentie is. De letters ***&apos;Adv.&apos;*** zijn nu wel beter zichtbaar i.t.t. dezelfde letters op een gele achtergrond. Daarentegen valt de groene achtergrond met de groene URL ernaast iets minder op. Het is maar net wat je focus pakt. Ik denk als Marketeer zijnde, ik toch de gele achtergrond prefereer. Het valt makkelijker weg met de witte achtergrond. Het valt iets minder op als je over een resultatenpagina &apos;scant&apos; met je ogen.
<h3 id="statistieken">Statistieken</h3>
<p>Ik zie in de statistieken minder kliks de laatste tijd, maar of er een correlatie bestaat met deze aanpassingen door Google, is moeilijk te zeggen. Seizoensinvloeden uitschakelen, evenals andere factoren, is iets wat alleen te realiseren is door veel tijd in een testsite te stoppen en daar ook bij te adverteren. Dat ligt niet binnen mijn hobby budget.</p>
<h2 id="paginatitellengteinserpsnippets">Pagina titel lengte in SERP snippets</h2>
<p>Niet alleen de manier van weergeven van de Adwords advertentie markeringen worden getest, ook zijn er af en toe grotere SERP snippets te zien in de zoekresultaten. Ik ben er nog geen tegen het lijf gelopen, maar de standaard breedte van 512px breed, wordt af en toe opgerekt tot 600px.</p>
<p>Hierdoor is er iets meer ruimte voor de inhoud van een zoekresultaat (organische resultaten dus). Dit houdt ook in, dat er wat meer letters getoond kunnen worden van de pagina titel en de meta-beschrijvingen.</p>
<h3 id="afkappen">Afkappen!</h3>
<p>Google kapt traditioneel te lange pagina titels af zodra ze deze lengte overschrijden. Woorden werden tot op heden gewoon bot afgekapt, ongeacht het een raar resultaat zou hebben.</p>
<p>Dit leidt soms tot zeer ongelukkige SERP snippets:<br>
<a href="https://d1avok0lzls2w.cloudfront.net/uploads/blog/5745c38fa33539.24952715.png"><img src="https://d1avok0lzls2w.cloudfront.net/uploads/blog/5745c38fa33539.24952715.png" alt="Pagina titels en SERP snippets in 2016 bij Google" loading="lazy"></a><br>
Dit had <em><strong>&apos;The International Association of Assemblages of Assassin Assets&apos;</strong></em> moeten zijn.</p>
<h3 id="pixelbreedteenfontverhoudingen">Pixel breedte en font verhoudingen</h3>
<p>Google gebruikt het Arial font voor de zoekresultaten, en het arial font heeft verschillende breedtes voor verschillende letters. Daarom is de 600px breedte eigenlijk de enige maat die je kan toepassen voor dit verhaal.</p>
<p>Gelukkig zijn er andere marketeers op de wereld die het hun sport hebben gemaakt dit tot op het bot uit te zoeken, en uit de blog <a href="https://moz.com/blog/title-tag-length-guidelines-2016-edition">&apos;Title Tag Length Guidelines: 2016 Edition&apos;</a>, is dit dan ook breed uitgemeten.</p>
<p>Het komt er op neer dat je met een pagina titel (het <code>&lt;title&gt;&lt;/title&gt;</code> element), het beste kunt proberen de titel onder de <em><strong>60 letters</strong></em><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> te houden. Dan loop je gemiddeld het minste risico dat Google zelf de titel gaat afkappen en laat eindigen met ...</p>
<h3 id="netjesafkappen">Netjes afkappen</h3>
<p>Een stukje terug gaf ik aan dat Google woorden soms ongelukkig afkapt. Ook lijkt het erop dat bij de nieuwe testen/wijzigingen, woorden niet meer zo maar halverwege worden afgekapt. Er wordt nu netjes voor of na het woord afgekapt. Is het een heel lang woord, dan zal het uiteindelijk alsnog worden afgekapt. Het afkap resultaat is dan wel behoorlijk rigoreus, ruim voordat de maximale lengte van 600px wordt behaald.</p>
<h3 id="merknameneraanvastplakken">Merknamen eraan vastplakken</h3>
<p>Google plakt tegenwoordig ook graag zelf de merknamen ergens achter aan de pagina titel vast. Wanneer een titel wordt ingekort want te lang, dan kan dit gebeuren. Ook dit heeft weer invloed op het uitzoeken van de juiste pagina titels.</p>
<h3 id="totslot">Tot slot</h3>
<p>Al het bovenstaande in acht nemende, is het belangrijk om nooit te vergeten dat de pagina titel relevant moet zijn ten opzichte van de pagina inhoud, en ook nog eens moet aanzetten tot interactie van de gebruiker (wil je bezoekers trekken).</p>
<p>Het is dan al gauw verleidelijk om een click-bait titel te gaan verzinnen, om maar bezoekers te trekken. Heeft ook geen zin! Zodra de pagina inhoud niet aan de verwachting van de bezoeker voldoet, zal deze vrij snel je site weer verlaten. Een bounce... En deze hebben ook weer invloed op de langere termijn voor je positie binnen de zoekresultaten.</p>
<p>Het is een complex geheel, om goed voor ogen te houden. Het is dan ook eigenlijk het makkelijkste, meest betrouwbare/zekere, om je gewoon aan de wensen van Google te houden. En die zijn simpel:</p>
<blockquote>
<p>Relevante pagina-titels en meta-beschrijvingen, die verwijzen naar een pagina met relevante inhoud welke de gebruiker waardeert.</p>
</blockquote>
<p>Kleinigheden waar je dan wel op kunt letten bij het schrijven van pagina-titels, meta-beschrijvingen en pagina-inhoud, zijn dingen als de lengte, om zo goed mogelijk op het netvlies van de eventuele bezoeker te landen.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Deze resultaten zijn gebaseerd op een Engelstalig onderzoek voor de Engels-sprekende markt, ofwel <a href="https://www.google.com">google.com</a>. Voor Nederland/Nederlandstalig, zou je vast kunnen houden aan een pagina titel onder de 62 letters. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Javascript alert() blokkeren zonder rechten op bron]]></title><description><![CDATA[Oh god, die lelijke popup schermpjes bovenaan je browser die je vaak over het hoofd ziet, maar wel de boel stoppen totdat je op ok klikt. Wat doen we eraan?]]></description><link>https://www.timhuesken.nl/javascript-alert-voorkomen-in-script-dat-je-niet-kan-aanpassen/</link><guid isPermaLink="false">62a224247b4523000101d781</guid><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Tue, 05 Jul 2016 11:51:39 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/o-TRIGGER-WARNING-facebook.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/o-TRIGGER-WARNING-facebook.jpg" alt="Javascript alert() blokkeren zonder rechten op bron"><p>In mijn dagelijks werk maak ik gebruik van oplossingen door derde partijen, en dat brengt bepaalde afhankelijkheden met zich mee.</p>
<h2 id="desituatie">De situatie</h2>
<p>Wij maken gebruik van formulieren welke door een 3e partij worden gegenereerd. Het formulier wordt op basis van een xml bestand ge&#xEF;nitialiseerd (waar ik <strong>geen</strong> toegang tot heb), en met xslt tot bruikbare HTML verwerkt.</p>
<p>Het verwerken van het formulier wordt dan ook door hen afgevangen met een javascript wat ze in het formulier injecteren, en waar ik ook <strong>geen</strong> toegang tot heb. Dit stukje script verwerkt bij verzending de inhoud van het formulier tot bruikbare data en verzend het dan naar hun database.</p>
<p>In dit stukje script worden formuliervelden met ongeldige waarden via een alert aan de gebruiker kenbaar gemaakt.</p>
<p><img src="https://www.timhuesken.nl/content/images/2016/07/irritante-alert-statement.jpg" alt="Javascript alert() blokkeren zonder rechten op bron" loading="lazy"></p>
<h2 id="waaromwildeikdealertvoorkomen">Waarom wilde ik de alert voorkomen?</h2>
<p>Tegenwoordig verwacht ik bij een formulier dat er geen alert naar voren komt wanneer ik iets ben vergeten in te vullen. Zo&apos;n irritante popup met wat meldingen...</p>
<p>Dat werkt gewoon niet lekker. Dan moet ik onthouden wat er staat en zelf op het formulier (zeker bij een groter formulier met veel invulvelden nogal irritant) gaan zoeken naar de plek waar ik iets ben vergeten of fout heb gedaan...</p>
<p>Ik wil gewoon met kleur in het formulier zien dat iets niet klopt en daar direct mee aan de slag. Een tekstboodschap en de focus naar het veld in kwestie is gewoon logisch en handiger.</p>
<p>Achteraf gezien grappig dat ik eerst de hele DOM structuur heb aangepakt met javascript, om de functies van het script van de 3e partij te omzeilen. Ik was de bron van het probleem aan het aanpakken, ofwel voorkomen dat de alert werd aangeroepen. Normaliter een goede aanpak, maar hier bleek ik aan symptoombestrijding te moeten gaan doen...</p>
<h2 id="hoeishetdantochgelukt">Hoe is het dan toch gelukt?</h2>
<p>Zoals net al min of meer gezegd, eigenlijk is het altijd simpeler dan je op een gegeven moment bedenkt...</p>
<pre><code class="language-html">    &lt;script&gt;    
    window.alert = function(){};
    &lt;/script&gt;
</code></pre>
<p>Ja, het is een beetje een grove oplossing, want ik blokkeer hiermee alle alert calls voor deze pagina. Maar op deze pagina gebruik ik geen alerts, en liever sowieso niet. Daarnaast draait het formulier in een iframe, en blijft hiermee de blokkade van alerts tot alleen de iframe beperkt.</p>
<p>Soms is het zo simpel... :)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Bluetooth toetsenbord koppelen aan Raspberry Pi 3]]></title><description><![CDATA[Gewoon een simpele set instructies om je bluetooth toetsenbord aan je raspberry pi 3 te koppelen. Benodigdheden? Een toetsenbord zonder bluetooth 🤣]]></description><link>https://www.timhuesken.nl/bluetooth-toetsenbord-koppelen-aan-raspberry-pi-3/</link><guid isPermaLink="false">62a224247b4523000101d77f</guid><category><![CDATA[rpi]]></category><category><![CDATA[bluetooth]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Mon, 14 Mar 2016 14:27:52 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/rpi3.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/rpi3.jpg" alt="Bluetooth toetsenbord koppelen aan Raspberry Pi 3"><p>Leuk om als cadeau te geven! Dat was het eerste wat door mij heen ging toen mijn schoonvader zijn lijstje met verjaardagscadeaus liet zien. Er stond waarempel een Raspberry Pi op het lijstje. En nog wel bovenaan. Zelf heb ik nog een Raspberry Pi 1B in gebruik als Media center waarmee ik al mijn foto&apos;s en filmpjes aan de diverse apparaten in huis beschikbaar stel.</p>
<h2 id="oldskoolspanclassparagraafstijlahrefbluetoothnaardeoplossingaspan">Oldskool <span class="paragraaf-stijl"><a href="#bluetooth">naar de oplossing &#xBB;</a></span></h2>
<p>Het idee van mijn schoonvader was om weer eens lekker &apos;oldskool&apos; te kunnen prutsen en wellicht her en der programmeren. Natuurlijk heb ik hem gelijk gewezen op de verschillende communities waar allerlei leuke Raspberry projecten op vermeld staan.</p>
<p>Iedereen in de familie vond het wel een goed cadeau eigenlijk en het was aan mij om het te regelen. Ten tijde van zijn verjaardagswens was de Raspberry Pi 2 de laatste telg in de Rpi familie. Net toen ik deze besteld had, kwam de 3 uit en moest ik bekennen dat ik dat feit even over het hoofd had gezien bij het bestellen. Oeps! Na wat gedoe met ruilen (wat ook weer helemaal goed is gekomen), kreeg ik gisteren een berichtje dat het niet helemaal leek te werken. Geen beeld, niets. Standaard als mij dat zelf gebeurt (alles goed? ja? stroom aan... en dan niets...), krijg ik het heel kort even warm. Zo van, is het nu DOA?</p>
<p>Om maar niet direct een heel RMA proces te moeten starten, toch maar even kijken of we konden troubleshooten en componenten konden uitsluiten.</p>
<p>Dus gezellig, schoonpa &apos;s avonds op bezoek met de Rpi 3, en dan onderdelen van mijn werkende Rpi 1 uitwisselen en het probleem isoleren. Biertje erbij, hopen dat het geen nachtwerk wordt.</p>
<p>Na een tijdje uitsluiten blijkt dat er niets aan de hand is met de aangekochte hardware! Dat gebeurt mij niet vaak als er iets mis is. Op dit moment is het enige dat nog overblijft de HDMI poort van de TV bij mijn schoonvader, of misschien toch zijn usb toetsenbord/muis die hij niet had meegenomen. Dat konden we niet testen.</p>
<h2 id="raspberrypi3engentegreerdebluetoothanamebluetootha">Raspberry Pi 3 en ge&#xEF;ntegreerde bluetooth<a name="bluetooth"></a></h2>
<p>Wel had hij zijn Bluetooth toetsenbord bij, en vandaar deze topic! Zo, dat was nog eens een lange intro voor mijn doen. Daarom ook de &apos;snelkoppeling&apos; optie bovenin.</p>
<p>Mijn schoonvader gaf aan dat hij het liefst draadloos kon typen met zijn bluetooth toetsenbord, maar na succesvol opstarten konden we nog niet direct gaan typen helaas. Het volgende speelde namelijk: Raspberry Pi met NOOBS ofwel Raspbian als besturingssysteem bevat in de meest recente versie wel degelijk Bluetooth ondersteuning, maar deze is niet zo conventioneel toegankelijk via instellingen in de menu&apos;s van Raspbian (via de GUI).</p>
<blockquote>
<p>Dat is gewoon &apos;ouderwets&apos;, of eigenlijk &apos;nieuwerwets&apos; terminal werk.</p>
</blockquote>
<h2 id="stapvoorstapinstructies">Stap voor stap instructies</h2>
<p>Beetje <a href="https://www.raspberrypi.org/forums/viewtopic.php?f=28&amp;t=138145#p920306">Googlen</a> (was er zelf ook niet van op de hoogte) en prutsen, bood de volgende oplossing:</p>
<ol>
<li>Open een Terminalsessie en voer in (gevolgd door enter):</li>
</ol>
<ul>
<li><code>sudo apt-get install pi-bluetooth</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></li>
<li><code>sudo apt-get update</code>[^2]</li>
<li><code>sudo reboot</code></li>
<li>Open de Terminal sessie weer:</li>
<li>Zet je bluetooth apparaat in discover modus</li>
<li><code>sudo bluetoothctl</code></li>
<li><code>agent on</code></li>
<li><code>default-agent</code></li>
<li><code>scan on</code></li>
<li><code>pair </code><strong><code>xx:xx:xx:xx:xx</code></strong>, waarbij xx de nummers en letters zijn die je ziet staan bij het apparaat wat het meest op je bluetooth toetsenbord lijkt nadat je de vorige opdracht <code>scan on</code> hebt uitgevoerd.</li>
<li>Waarschijnlijk wordt er nu een PIN code getoond, toets deze in op je toetsenbord (<mark>op je bluetooth toetsenbord</mark>), gevolgd door [ENTER].</li>
<li><code>trust </code><strong><code>xx:xx:xx:xx:xx</code></strong> (Soms werkt dit ook als er geen PIN code wordt gevraagd of gegeven)</li>
<li><code>connect </code><strong><code>xx:xx:xx:xx:xx</code></strong></li>
</ul>
<p>Hieronder nog even alleen de opdrachten voor gemakkelijk kopi&#xEB;ren en plakken voor in de terminal:</p>
<pre><code class="language-bash">    sudo apt-get install pi-bluetooth \
    sudo apt-get update \
    sudo reboot \
</code></pre>
<p>Na de reboot:</p>
<pre><code class="language-bash">    sudo bluetoothctl
</code></pre>
<p>Binnen de bluetoothctl:</p>
<pre><code class="language-bash">    agent on
    default-agent
    scan on
    pair xx:xx:xx:xx:xx
    trust xx:xx:xx:xx:xx
    connect xx:xx:xx:xx:xx
</code></pre>
<p>Veel plezier vandaag,</p>
<blockquote>
<p>tijdens <a href="https://en.wikipedia.org/wiki/Pi_Day">internationale Pi-dag!</a></p>
</blockquote>
<p>met het tikken via Bluetooth op je Raspberry Pi 3 via Bluetooth! Ook na een reboot blijft dit prima actief en werken. Dit houdt natuurlijk wel op zodra je het SD-kaartje weer reset.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Wanneer er wordt aangegeven dat deze al bestaat, is het eigenlijk al goed. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Last van spam in je Analytics statistieken? Dit is een oplossing!]]></title><description><![CDATA[Spam in Analytics, het is er in vele vormen en maten. Ghost referral spam is een standaard spam tegenwoordig. Het eerste wat ik doe bij een nieuwe property is deze filters en segmenten aanmaken.]]></description><link>https://www.timhuesken.nl/last-van-spam-in-je-analytics-statistieken-dit-is-een-oplossing/</link><guid isPermaLink="false">62a224247b4523000101d77d</guid><category><![CDATA[SEO]]></category><category><![CDATA[Analytics]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 21 Jan 2016 12:17:23 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2016/11/stop-spam-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2016/11/stop-spam-1.jpg" alt="Last van spam in je Analytics statistieken? Dit is een oplossing!"><p>Vrijwel ieder Analytics account krijgt ermee te maken, bezoeken die 0:00 seconden duren, niets uitvoeren op je site (geen conversies of vervolgpagina&apos;s bezocht) en je gemiddelden omlaag halen. Je bounce-rates schieten omhoog als je een kleine site bent en je weet gewoon niet waar je aan toe bent.</p>
<p>Beter bekend als <code>Ghost referral spam</code>, is dit gewoonweg irritant. Gelukkig zijn er een aantal manieren om dit op te lossen.</p>
<h2 id="stelfiltersin">Stel filters in</h2>
<p>Stel filters in op elke relevante <code>Weergave</code> of View die je gemaakt hebt binnen je Analytics account. Je kunt dit met de hand doen, maar dan ben je wel even zoet. Tevens moet je dan elke keer dat er weer een andere soort spammer langs komt, een nieuw filter bouwen of een bestaande aanpassen. Straks ben je nog meer bezig met spam uit je overzichten halen, dan met het bekijken van statistieken.</p>
<p>Er zijn meer mensen die dit hebben gedacht, en een stel slimmeriken hebben dit inmiddels lekker geautomatiseerd. Bezoek 1 van de volgende sites en maak gebruik van hun gratis tools en laat deze tools lekker de filters onderhouden voor je:</p>
<ul>
<li><a href="http://referrer-spam.help/">Referrer-spam.help</a></li>
<li><a href="http://www.simoahava.com/spamfilter/">Spam filter installer</a></li>
</ul>
<p>Besef je wel dat deze oplossing (inherent aan een filter in Analytics), niet met terugwerkende kracht zal opereren. Wat er dus al staat, blijft in je statistieken bestaan. Dus, eigenlijk, wacht niet langer!</p>
<p>Eventueel is er nog wel een manier om ook met terugwerkende kracht de gegevens uit je statistieken te verbergen. Let wel, dit is verbergen, i.t.t. het niet meer registreren via een filter.</p>
<h2 id="maakspecialesegmentenaan">Maak speciale segmenten aan</h2>
<p>Bij elke Weergave of View in je Analytics account, wordt een set segmenten aangeleverd. Dit zijn eigenlijk gewoon verzamelingen van bezoekersdata, met een speciale verdeling, ofwel segmentering. Het leuke hiervan is dat je deze segmenten dus kunt loslaten op je data waar al vervuiling door <code>Ghost referral spammers</code> optreedt. Zo&apos;n segment verbergt dan mooi de spam data en kan samen met een <code>Alle gegevens</code> segment in 1 grafiek worden getoond. Zie je meteen welk deel van je bezoekers een spammer is en wat de verschillen in gedrag op je site wel niet zijn.</p>
<p>Ik heb zo&apos;n segment aangemaakt en je kunt deze importeren naar je segmenten bibliotheek door deze <a href="https://analytics.google.com/analytics/web/template?uid=WxNI7QOTTXCxIt_mYJPvMw">link</a> te volgen. Verander bijvoorbeeld regel 1 en 2 van het segment van jouw-domein.nl naar je eigen website adres. &#xC9;&#xE9;n versie met en &#xE9;&#xE9;n zonder www.</p>
<p>Selecteer in het overzicht naar keuze daarna je segment en je zult wellicht een verschil gaan zien in bezoekers.</p>
<h2 id="welkeoptietoepassen">Welke optie toepassen?</h2>
<p>Ik zeg in dit geval, &apos;Why not both?&apos;. De 1 sluit de ander niet uit en wanneer je bijvoorbeeld filters hebt gebruikt, kan het zo zijn dat er een nieuwe verwijzingsspammer bij komt in de loop van de tijd, en je niet goed door hebt dat dit langzaam de statistieken aan het vervuilen is. Daar komt het segment wat ik eerder aanraadde, weer goed van pas.</p>
<p>Het segment haalt namelijk verkeer wat niet aan een voor jouw bekende hostname vandaan komt, eruit. Dus het segment <code>includes</code> alle verkeer van bijvoorbeeld jouw-website-adres.nl, facebook.com, linkedin.com, etc. Nu heb je een segment wat andersom werkt dan een filter over het algemeen. Deze blokkeert ofwel werkt op <code>exclude</code> basis.</p>
<p>Daarom is het zo interessant om beiden te gebruiken!</p>
<p>Succes ermee! Als er vragen zijn, stel ze in de comments hieronder.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Je website versnellen met een CDN]]></title><description><![CDATA[Duurt lang! En toch wil je dat hoge resolutie plaatje wel blijven aanbieden aan je bezoekers... resizen en comprimeren is geen optie. Wat dan? een Content Delivery Network kan uitkomst bieden.]]></description><link>https://www.timhuesken.nl/je-website-versnellen-met-een-cdn/</link><guid isPermaLink="false">62a224247b4523000101d77c</guid><category><![CDATA[CDN]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Mon, 18 Jan 2016 15:27:07 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/sun_flare_clouds_sky_1920x1080.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/sun_flare_clouds_sky_1920x1080.jpg" alt="Je website versnellen met een CDN"><p>Als online marketeer kom je op een gegeven moment op een punt waarbij je je afvraagt; Kan mijn site nog sneller? En dan ga je de siteconfiguratie optimaliseren. Scripts in de footer, A-synchronous inladen waar mogelijk. CSS bestanden minify-en, en waarom de rest ook niet? HTML, Javascript... allemaal comprimeren die hap. Afbeeldingen optimaliseren. Zo dat scheelt. En nu? Wat kan er nog meer gedaan worden?</p>
<p>Een snellere server, meer capaciteit regelen? Als het een VPS is, waarom niet wat meer resources laten toewijzen voor een schappelijke prijs?</p>
<h2 id="quickwin">Quickwin</h2>
<p>Of, en dat is een quickwin heb ik mogen ondervinden, gebruik een CDN. Een Content Delivery Network. Deze uitbaters hebben capaciteit genoeg en een super geoptimaliseerde infrastructuur klaarstaan voor jou en vele anderen.</p>
<p>Ik ben na enig zoeken maar eens met Cloudflare aan de gang gegaan. Ze hebben een gratis plan wat voor een simpel blogje of eenvoudige website al genoeg is over het algemeen. Ook krijg je er gratis de mogelijkheid bij om via het beheerderspaneel aan te geven dat je site onder vuur ligt. Nu zal ik daar waarschijnlijk niet mee te maken krijgen, maar het meldpunt vuurwerkoverlast had er rond oud en nieuw jongstleden last van, en dan is het toch wel handig als je met de klik van de muis de DDoS knop kan aanzetten en je site daarmee bereikbaar blijft.</p>
<p>Het proces is eenvoudig en ik zal hier dan ook niet uitvoerig over praten. Volg de instructies op hun site, stel de nameservers in bij je domein registrar en 24 uur geduld. Eigenlijk was dit het al een beetje.</p>
<p><em>Besef wel dat alle externe resources, zoals op mijn site bijvoorbeeld de Comment-sectie via Disqus en Google Analytics, niet via een CDN zullen profiteren van de caching functies.</em></p>
<h2 id="dewinst">De winst</h2>
<p>Mijn site is er significant sneller door geworden en mijn laadtijd is ~25% sneller geworden (van 1,9s naar 1,5s), er is via de caching servers van Cloudflare nog steviger gecomprimeerd dan dat ik al had en dat scheelt ook een goede 25%.</p>
<p>En dit alleen al met een vrij eenvoudige site is toch het vermelden waard. Kun je nagaan wat de winsten kunnen zijn bij een site die al een tijdje in de lucht is en her en der plugins en modules en aardig wat content heeft draaien...</p>
<p>Mijn ijkpunten zijn de tools van <a href="https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fwww.timhuesken.nl%2F&amp;tab=desktop">Google Pagespeed Insights</a> en <a href="https://gtmetrix.com/?url=https://www.timhuesken.nl">GTMetrix.com</a> geweest. Ik ben best wel tevreden over mijn scores nu.</p>
<p>Doe er je voordeel mee! En nee ik wordt niet gesponsord door <a href="https://www.cloudflare.com">Cloudflare</a> :-)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Zelf hosten van je eigen blog (met Ghost)]]></title><description><![CDATA[Je eigen blog opzetten en hosten kan een hele toer zijn. Tegenwoordig is het best eenvoudig. Heb je een Synology? Dan heb ik hier een guide die je er stapsgewijs doorheen helpt.]]></description><link>https://www.timhuesken.nl/zelf-hosten-van-je-eigen-blog/</link><guid isPermaLink="false">62a224247b4523000101d77b</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Nginx]]></category><category><![CDATA[Synology]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Tim Huesken]]></dc:creator><pubDate>Thu, 17 Dec 2015 10:13:00 GMT</pubDate><media:content url="https://www.timhuesken.nl/content/images/2017/05/Blog_1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://www.timhuesken.nl/content/images/2017/05/Blog_1.jpg" alt="Zelf hosten van je eigen blog (met Ghost)"><p>Zelf je eigen blog hosten klinkt misschien als een hele toer, maar eigenlijk valt het best wel mee. Ja, je moet wel een beetje handig zijn met computers, en je schoonmoeder zal het vast niet voor elkaar krijgen zonder hulp. Er is genoeg over geschreven en als je toch ergens vast loopt, is er genoeg hulp te krijgen op diverse fora.</p>
<p>In deze blogpost neem ik als voorbeeld het zelf hosten van je blog op een Synology NAS, en heb ik gekozen voor <a href="https://www.ghost.org">Ghost blog</a> software. Het leuke is, dat je met deze methode met hetzelfde gemak straks ook Wordpress, Joomla of Drupal zou kunnen starten. En wat helemaal mooi is, je hebt het eerste deel van deze handleiding dan al uitgevoerd, dus dat scheelt weer tijd! Dan kan je letterlijk binnen 5 minuten een volledig onafhankelijk zelf gehost blog draaiende hebben.</p>
<p>Wanneer je het voorbereidend werk meetelt, een Synology NAS<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> in bezit hebt, en je hebt deze ge&#xFC;pdatet naar op zijn minst versie 5.2, kun je binnen 15 minuten al aan het tikken zijn en je eerste blogpost schrijven.</p>
<p>Het is een aardige handleiding geworden, en ik raad dan ook aan om deze van start tot eind te doorlopen. Hieronder toch maar even een inhoudsopgave, zodat het allemaal wat overzichtelijker wordt hopelijk.</p>
<p><strong>INHOUDSOPGAVE</strong></p>
<ol>
<li><a href="#1">Je Synology voorbereiden</a><br>
1.1 <a href="#11">Internetverkeer goed doorsturen</a><br>
1.2 <a href="#12">Package Center</a><br>
1.2.1 <a href="#121">Docker installeren</a><br>
1.3 <a href="#13">Binnenkomend verkeer op Synology naar de juiste containers sturen</a><br>
1.3.1 <a href="#131">Start Docker</a><br>
1.3.2 <a href="#132">Software zoeken en downloaden</a><br>
1.3.3 <a href="#133">Software starten in eigen container</a><br>
1.3.4 <a href="#134">Nginx container configureren</a><br>
1.3.4.1 <a href="#1341">Controleren Nginx container</a><br>
1.3.5 <a href="#135">Nginx container starten</a></li>
<li><a href="#2">Ghost blog installeren en instellen</a><br>
2.1 <a href="#21">Ghost blog zoeken, downloaden, instellen</a><br>
2.2 <a href="#22">Ghost blog starten</a><br>
2.2.1 <a href="#221">Ghost is wel gestart, maar ik kan er niet bij via de browser!</a><br>
2.3 <a href="#23">Lekker bloggen!</a></li>
</ol>
<p><mark>Wat het makkelijker maakt straks om je blog direct online aan te bieden, is dat je alvast een domeinnaam hebt gekocht en deze naar je ip-adres van je router hebt doorgestuurd of dit laten doen. Ik heb bijvoorbeeld www.timhuesken.nl gekocht voor &#x20AC;9 per jaar.</mark><br>
<a name="1"></a></p>
<h2 id="1jesynologyvoorbereiden">1. Je Synology voorbereiden</h2>
<p>Er zijn een aantal dingen die we willen instellen op de Synology, zodat je blog straks direct te vinden is, ook van buitenaf. We gaan zorgen dat internetverkeer netjes bij je Synology terecht komt door de poorten goed in te stellen op je router, en we gaan de onderliggende software instellen die ons straks in staat stelt om met een simpele opdracht het blog te starten.<br>
<a name="11"></a></p>
<h3 id="11routerinstellenvoorjesynology">1.1 Router instellen voor je Synology</h3>
<p>Zorg ervoor dat je Synology een verbinding met het internet heeft, en dat binnenkomend verkeer op poort 80 netjes naar je Synology wordt doorgestuurd. Wanneer iemand naar een website gaat, wordt er door elke browser standaard uitgegaan dat men naar poort 80 wilt gaan, tenzij anders aangegeven. Om straks wat gerommel met poorten te voorkomen, is het verstandig om een poort door te sturen op je router. In dit geval poort 80 naar het interne netwerk (naar het ip-adres van je Synology) op poort 8080 (dit mag ook een andere poort zijn). Dit geldt vanaf DSM 6.x, ofwel xpenology 6.x ook voor de https poort 443. Ik kwam er recent achter dat dit dan niet meer goed werkt. Pak dus bijvoorbeeld poort 4433.</p>
<blockquote>
<p><strong>Kort gezegd: Router &gt; poort doorsturen &gt;</strong></p>
</blockquote>
<blockquote>
<ul>
<li><strong>externe poort 80, doorsturen naar interne poort 8080</strong></li>
<li><strong>externe poort 443 doorsturen naar interne poort 4433<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup></strong></li>
</ul>
</blockquote>
<p>Wanneer je dit hebt geregeld, ga je naar het beheersgedeelte van je Synology, vaak te vinden op het ip-adres van je Synology, poort 5000. Bijvoorbeeld zal de volgende URL je hier brengen: <code>http://192.168.1.2:5000</code>. Log in met het beheerdersaccount en wachtwoord en ga naar het &apos;Package Center&apos;.<br>
<a name="12"></a></p>
<h3 id="12packagecenter">1.2 Package Center</h3>
<p>In het Package Center van Synology staan allemaal kant-en-klare apps met allerlei soorten software. Omdat er nog geen package is voor Ghost, gaan we dit op een andere manier oplossen. Er is namelijk een stukje software, Docker, wat je Synology zal omtoveren tot een apparaat wat eigenlijk alle software die er is, zou kunnen gaan draaien. Het bouwt als het ware een virtuele omgeving bovenop het bestaande bestuurssysteem en leent de basis functies van dit bestuurssysteem binnen de verschillende &apos;zandbakken&apos; die je kunt opstarten. Binnen Docker noemen ze dit &apos;containers&apos;.</p>
<p>Zo&apos;n container moet je zien als een afgebakende omgeving, waar jij bepaald wat er wordt gedaan en uitgevoerd. Hier gaan we straks Ghost laten draaien. Docker is daarnaast in staat om op een veilige manier te communiceren met de wereld buiten de zandbak, waardoor jij nu bijvoorbeeld in staat bent om deze blogpost te lezen!<br>
<a name="121"></a></p>
<h4 id="121dockerinstalleren">1.2.1 Docker installeren</h4>
<p>Zoek in het Package Center naar het pakket van Docker en klik op installeren. Zodra dit gebeurt is, ben je er eigenlijk al klaar voor om je blog op te gaan starten.</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/docker-installeren.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"></p>
<p>Om ervoor te zorgen dat het internetverkeer je blog netjes weet te vinden, zijn er nog wel wat instellingen nodig die we moeten uitvoeren wanneer we het blog willen gaan starten.<br>
<a name="13"></a></p>
<h3 id="13binnenkomendverkeeropsynologynaardejuistecontainerssturen">1.3 Binnenkomend verkeer op Synology naar de juiste containers sturen</h3>
<p>We hebben hiervoor de router al juist ingesteld en ervoor gezorgd dat de basis voor je Ghost blog er staat. Nu moeten we alleen nog zorgen dat we straks ook onze container gemakkelijk op de juiste manier kunnen instellen.</p>
<p>Het internetverkeer komt aan op je Synology, maar we moeten dit nog naar de juiste container leiden, en ervoor zorgen dat deze container straks ook snapt wat het met deze verzoeken moet doen.</p>
<p>Om het verkeer naar de juiste zandbakken van Docker te leiden, is er een simpele ingreep nodig. Start Docker op binnen je beheersomgeving van je Synology. Je kunt deze vinden door linksboven de lijst met apps op te vragen. Hier staat Docker tussen.<br>
<a name="131"></a></p>
<h4 id="131startdocker">1.3.1 Start Docker</h4>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/docker-starten.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"></p>
<p>Nadat je Docker hebt gestart zul je een overzichts pagina zien waar nu nog weinig informatie zal staan.</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/docker-synology.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"><br>
<a name="132"></a></p>
<h4 id="132softwarezoekenindownloaden">1.3.2 Software zoeken in downloaden</h4>
<p>Klik nu op Register en zoek naar <code>jwilder</code> of <code>nginx</code>. We zijn op zoek naar de &apos;Automated Nginx reverse proxy for docker containers&apos; van jwilder. Klik het zoekresultaat aan en klik als laatste op downloaden.</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/nginx-proxy-installeren.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"><br>
<a name="133"></a></p>
<h4 id="133softwarestartenineigencontainer">1.3.3 Software starten in eigen container</h4>
<p>Wanneer de software is gedownload, kunnen we naar de volgende stap, en dat is &apos;Image&apos;. Zoek binnen dit gedeelte naar de software die we hebben laten downloaden, selecteer deze door er 1 keer op te klikken en kies voor &apos;Starten&apos;. Kies daar voor Starten met Docker uitvoeren voor nu.</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/nginx-proxy-starten.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"><br>
<a name="134"></a></p>
<h4 id="134nginxcontainerconfigureren">1.3.4 Nginx container configureren</h4>
<p>We krijgen nu een simpel scherm te zien waar wordt gevraagd een opdrachtregel in te vullen. Vul hier de volgende code in:</p>
<pre><code class="language-bash">docker run -d --name nginx -p 8080:80 -p 4433:443 -v /volume1/docker/nginx/certs:/etc/nginx/certs -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
</code></pre>
<p>Deze opdrachtregel is uniform en maakt het makkelijker voor nu om snel te starten. Je hoeft nu namelijk alleen nog even door de opties heen te lopen en ze te controleren. Invoeren hoeft nu niet meer. Dit is zo, omdat we voor de optie zonder wizard hebben gekozen bij de vorige actie.</p>
<p><strong><mark>EDIT</mark></strong>:</p>
<blockquote>
<p>Het kan zijn dat je de opdrachtregel niet via de wizard kan invoeren zoals hier beschreven. Het kan echter wel gedaan worden m.b.v. de command-line via een SSH verbinding.</p>
</blockquote>
<p>&#xA0;</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/nginx-opdrachtregel.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"><br>
<a name="1341"></a></p>
<h4 id="1341controlerennginxcontainer">1.3.4.1 Controleren Nginx container</h4>
<p>Doorloop nu de informatie door een aantal keer op volgende te klikken en bekijk de gegevens even. Misschien heb je een tik-fout gemaakt?</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/nginx-controle.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"><br>
<a name="135"></a></p>
<h4 id="135nginxcontainerstarten">1.3.5 Nginx container starten</h4>
<p>Wanneer je bij het laatste scherm bent aangekomen, vergeet dan niet het vinkje &apos;Voer deze container uit nadat de wizard is voltooid&apos; aan te vinken zoals hier beneden aangegeven.</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/container-starten.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"></p>
<p>Voila, je hebt nu een webserver die verzoeken van het internet afvangt en deze netjes kan doorsturen naar de juiste container(s).</p>
<p>Ik heb je dit laten doorlopen zodat je straks met het grootste gemak meerdere blogs zou kunnen starten, mocht je dit graag willen. Of bijvoorbeeld andere software wilt gaan draaien en het internetverkeer op een nette manier snel en effici&#xEB;nt naar de juiste container wilt gaan loodsen.</p>
<p>Credits ook voor <a href="http://jasonwilder.com/">Jason Wilder</a>, die een <a href="http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/">volledig ingestelde image van Nginx</a> beschikbaar heeft gesteld, die in staat is om dit allemaal in goede banen te leiden, zonder tussenkomst van jou, de beheerder. Anders zou je namelijk bij iedere blog of website die je zou willen aanbieden, de hele regels voor het routeren van het verkeer, iedere keer opnieuw handmatig moeten gaan aanmaken.</p>
<p>Dit is de voorbereiding die nodig is om nu (eindelijk?!) je Ghost blog te kunnen gaan opstarten en lekker te kunnen gaan bloggen.<br>
<a name="2"></a></p>
<h2 id="2ghostbloginstallereneninstellen">2. Ghost blog installeren en instellen</h2>
<p>Je Synology is nu helemaal voorbereid om je eigen Ghost blog te gaan hosten. Eventueel kun je ook allerlei andere software gaan laten draaien, maar daar heb ik het in deze blogpost nu niet over.</p>
<p>Ik heb dit blog draaien op Ghost, waar ik een tijdje geleden tegenaan liep toen ik Wordpress eigenlijk wel een beetje zat was. Ik had behoefte aan iets nieuws (esthetisch ben ik ook erg gecharmeerd van de standaard looks van Ghost - dat Casper als thema standaard mee levert) en Ghost is technisch gezien moderner, lichter qua server belasting en gewoon lekker simpel en kaal. Dat is ook zo&apos;n beetje hun motto, &apos;<a href="http://john.onolan.org/project-ghost/">Just a blogging platform</a>&apos;.<br>
<a name="21"></a></p>
<h3 id="21ghostblogzoekendownloadeninstellen">2.1 Ghost blog zoeken, downloaden, instellen</h3>
<p>Goed, we gaan nu wat sneller door de stappen van het maken van een Docker container heen, want eigenlijk is het niet veel spannender dan wat we eerder hebben gezien bij de voorbereiding.</p>
<p>Start Docker op je Synology en zoek in het register naar Ghost. Als het goed is zie je de volgende resultaten:</p>
<p>[<img src="https://www.timhuesken.nl/content/images/2015/12/ghost-image-downloaden.jpg" alt="Zelf hosten van je eigen blog (met Ghost)" loading="lazy"></p>
<p>Ga nu naar het onderdeel Image en selecteer Ghost en kies voor &apos;Starten met Docker uitvoeren&apos;, want we gaan weer met een opdrachtregel starten, voor het gemak en de snelheid.</p>
<pre><code class="language-bash">docker run --name mijn-ghostblog -d -p 84:2368 -e VIRTUAL_PORT=84 -e VIRTUAL_HOST=www.jouwdomeinnaam.nl,jouwdomeinnaam.nl -e NODE_ENV=production -v /volume1/docker/ghost/mijn-ghostblog:/var/lib/ghost ghost
</code></pre>
<p><mark>Voordat je de container start, lees onderstaand even rustig door:</mark></p>
<p>Hierboven staat in de code <code>www.jouwdomeinnaam.nl,jouwdomeinnaam.nl</code>. Voor mijzelf heb ik hier natuurlijk www.timhuesken.nl ingevuld, maar als je dus zelf een blog wilt starten is dit nu de plek waar je jouw eigen domein invuld.</p>
<p><strong>Wat doet dit commando verder?</strong></p>
<ul>
<li>Het maakt een container met de naam <code>mijn-ghostblog</code> aan en laat deze permanent draaien <code>-d</code> als daemon</li>
<li>Wij vertellen Docker dat de container verkeer op poort 84 wil ontvangen <code>-p 84:2368</code> en dat de container intern het verkeer naar Ghost doorstuurt op poort 2368, wat een standaard poort voor Ghost is.</li>
<li>Omdat we aangeven dat de <code>-e VIRTUAL_HOST=www.jouwdomeinnaam.nl,jouwdomeinnaam.nl</code> is, zal Nginx alle aanvragen voor deze URLs netjes doorsturen naar deze container. Dit is dus mogelijk omdat we de Nginx software van Jason Wilder gebruiken</li>
<li>De container zal via Nginx het verkeer op poort 84 ontvangen dankzij <code>-e VIRTUAL_PORT=84</code>.</li>
<li>We geven aan dat de basis van Ghost, Node.js, onze Ghost installatie in Productie mode moet gaan draaien met behulp van <code>-e NODE_ENV=production</code>. Hier kunnen we dadelijk gebruik van maken bij het instellen van Ghost.</li>
<li>En als laatste maar niet geheel onbelangrijk; <code>-v /volume1/docker/ghost/mijn-ghost-blog:/var/lib/ghost</code> zorgt ervoor dat op mijn Synology er een folder wordt gelinkt aan de folder binnen Ghost, waar alle belangrijke informatie staat. En dit laatste is ook direct het mooie van Docker. <mark>Ik kan straks de container weggooien, of als deze om wat voor reden dan ook niet meer wil werken... Ik heb dankzij deze link al mijn gegevens nog!</mark></li>
<li>Het allerlaatste stukje aan het eind van de regel <code>ghost</code>, na de gelinkte folder, is niets meer dan de opdracht aan Docker om de image van Ghost te gebruiken.</li>
</ul>
<p>Misschien geeft bovenstaand iets meer inzicht in hoe Docker nu in elkaar steekt en wat je er eigenlijk allemaal mee kunt bouwen. Maar goed, dat is voor een andere keer. Laten we nu eindelijk eens het blog gaan starten!<br>
<a name="22"></a></p>
<h3 id="22ghostblogstarten">2.2 Ghost blog starten</h3>
<p>Wanneer je eerder genoemde opdrachtregel weer door de &apos;Start met Docker uitvoeren&apos; haalt, en kiest om de container uit te voeren wanneer de wizard voltooid is, kun je nu naar www.jouwdomeinnaam.nl gaan en zul je Ghost zien draaien!</p>
<p>Mocht dit niet zo zijn, dan is er nog 1 dingetje wat we even moeten doen, en dat is het volgende:<br>
<a name="#221"></a></p>
<h4 id="221ghostiswelgestartmaarikkanernietbijviadebrowser">2.2.1 Ghost is wel gestart, maar ik kan er niet bij via de browser!</h4>
<p>Het kan zijn dat, en waarschijnlijk is dat ook zo, we nog even Ghost moeten instellen op de achtergrond.</p>
<p>Sinds de komst van versie 2.0.0 van ghost zijn er een aantal zaken veranderd. Zo is er geen config.js meer zoals eerst beschreven in deze blogpost. Wat er nu in de plaats is gekomen is de mogelijkheid om via een Docker variabele de url in te stellen: <code>-e url=http://jouwdomeinnaam.nl.</code> Hierdoor wordt de <code>config.production.json</code> voorzien van de waarde van het domein waarop Ghost moet worden aangeboden. Mocht dit niet lukken, kan je altijd je container via de ssh terminal benaderen door <code>docker exec -it mijn-ghostblog bash</code> in te voeren. Hierna kun je naar de juiste plek navigeren en met commando <code>vi /var/lib/ghost/config.production.json</code> een aanpassing doen. Ga met de cursor naar de juiste regel waar de url staat vermeldt, druk op <code>insert</code>, ga naar het stukje met <code>http://localhost:2368</code> en vervang dit met <a href="http://jouwdomeinnaam.nl">http://jouwdomeinnaam.nl</a>. Druk op <code>escape</code>, gevolgd door <code>:w</code>, <code>ENTER</code>, <code>:q</code>, <code>exit</code>, <code>ENTER</code>.</p>
<p><a name="23"></a></p>
<h3 id="23lekkerbloggen">2.3 Lekker bloggen!</h3>
<p>Ga naar Docker en rechtsklik op je Container van Ghost. Kies voor &apos;Actie&apos; en klik vervolgens op &apos;Opnieuw opstarten&apos;. That&apos;s it!</p>
<p>Je blog moet nu toch echt werken. Ga naar www.jouwdomeinnaam.nl, stel wat zaken in en volg de tutorial van Ghost!</p>
<p>Is het nu toch nog niet gelukt? Laat even een reactie achter hieronder en ik zal je proberen te helpen.</p>
<p><mark>Eventueel zou je het volgende kunnen proberen:</mark></p>
<ol>
<li>Installeer de <a href="https://www.synology.com/nl-nl/dsm/app_packages/Node_js">node.js</a> package van Synology via het Package Center (de play edities kunnen namelijk wel node.js draaien, wat Ghost blog weer als basis gebruikt)</li>
<li>Open een SSH terminal via <a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html">Putty</a> naar je NAS</li>
<li>Gebruik de <a href="https://www.npmjs.com/package/ghost">volgende gids om Ghost blog</a> te installeren</li>
</ol>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><em>Vanaf versie 6.x van Synology blijkt dat poort 443 ook wordt doorgezet naar de management pagina, en dat er niets aan te veranderen is. Daarom deze oplossing op basis van je eigen router.</em> <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a> <a href="#fnref1:1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>