Merry Christmas,
enjoy your off-time with friends and family, as well as the transition into the new year. Looking forward to our work together in 2019.
Even during this small holiday break, I will be keeping an eye on things :)
Kind regards
Alex
ps. During the christmas holidays, baked apples are a must-have for me. Quick recipe below:
The GDPR makes us jump through a lot of hoops to cleanup our websites and make all our code compliant. Many aspects of the GDPR are far from completely defined yet and there is a great uncertainty what is required, what can stay and what needs to be adjusted right now.
Embeding Youtube videos is one area, that many are afraid of. You need to mention the use of youtube in your data privacy policy.
Something like that :”Our website uses plugins from YouTube, which is operated by Google. The operator of the pages is YouTube LLC, 901 Cherry Ave., San Bruno, CA 94066, USA.
If you visit one of our pages featuring a YouTube plugin, a connection to the YouTube servers is established. Here the YouTube server is informed about which of our pages you have visited.
If you’re logged in to your YouTube account, YouTube allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your YouTube account.
YouTube is used to help make our website appealing. This constitutes a justified interest pursuant to Art. 6 (1) (f) DSGVO.
Further information about handling user data, can be found in the data protection declaration of YouTube under https://www.google.de/intl/de/policies/privacy.”
The question remains, if that is actually enough?
Youtube allows you to switch to a cookieless embed on their website, that limits the data flowing to Google servers.
But how do you use that programmatically, with the Youtube iFrame API?
The iFrame API documentation has not been updated since 2014 and does not mention any option to switch to the cookieless youtube host.
But there is an easy option, just add the host option “https://www.youtube-nocookie.com” to your calls :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var player; var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); player = new YT.Player('player1', { wmode: 'transparent', host: 'https://www.youtube-nocookie.com', playerVars:{ wmode: 'transparent', showinfo:0, autohide:1, }, videoId: YOUR_VID_ID, events: { 'onReady': onPlayerReady } }); |
There we go, so simple and painless :)
The GDPR is a good thing, as it helps to secure our privacy. Those that are complaining now, are those that waited until the GDPR went live and did not take the time to really prepare soon enough.
BTW the email spam sent by so many services, was so not required, but helped me to clean up / delete those dormant accounts ;)
Pimp my Log is a web app written in PHP. It displays server logs friendly.
By default, Pimp My Log supports :
Key features:
R
, click on the refresh button or set an auto-refresh timerEasily add beautiful checkboxes and radio buttons in pure CSS. Highly customizable via SCSS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!-- add class p-default --> <div class="pretty p-default"> <input type="checkbox" /> <div class="state"> <label>Default</label> </div> </div> <!-- add class p-fill --> <div class="pretty p-default p-fill"> <input type="checkbox" /> <div class="state"> <label>Fill</label> </div> </div> <!-- add class p-thick --> <div class="pretty p-default p-thick"> <input type="checkbox" /> <div class="state"> <label>Thick</label> </div> </div> |
WooCommerce Appointments is a commercial booking plugin that allows you to setup appointments with WooCommerce. It has full integration into Google calendar to track appointments of your staff.
Staff availability can be set globally or via each staff members profile. While this is nice, I was looking for an option to actually handle availability via another Google Calendar as well. That was a must have feature for a current project.
What do you do, if that feature is not available yet ? You poke the code!
The magic entry point for the staff availability is the user meta “_wc_appointment_availability“, which is made available through includes/class-wc-product-appointment-staff.php.
A couple of weeks ago I asked support for a simple filter hook to alter the availability on demand from the outside.
The development team added the feature in one of the latest releases, making wc_appointments_staff_availability the entry point for my custom availability changes.
1 2 3 4 5 6 |
add_filter( 'wc_appointments_staff_availability', 'availability_callback', 10, 3 ); function availability_callback($availability, $staff){ // Your changes here return $availability; } |
You can either pull Google Calendar Events directly through the Google Calendar API or use the available iCal export option. In this quick example I will use the private calendar iCal export file.
Lets setup a quick clean calendar, called “Availability”. So simple and catchy :)
For this example I am using the PHP ICS Parser, but any other parser will do. Install it via composer: composer require johngrogg/ics-parser.
Lets create a quick little plugin to get us going and save it to /wp-content/plugins/CustomAvailability/smile.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php use ICal\ICal; /* Plugin Name: WooCommerce Appointments - Custom Availability Plugin URI: https://portalzine.de Description: Attach additional Google Calendar for staff availability Version: 1.0 Author: portalZINE NMN Author URI: https://portalzine.de */ require_once(PATH_TO_VENDOR_DIR."vendor/autoload.php"); class StaffCustomAvailability{ function __construct() { add_filter( 'wc_appointments_staff_availability', array($this, 'staffAvailability'), 10, 3 ); } function staffAvailability($availability, $staff){ // Your changes here return $availability; } } $StaffCustomAvailability = new StaffCustomAvailability(); |
Its time to get the data into the system. I am only pulling and altering the availability for one single user in this example, the user with the USERID “3”. This should provide you with a good starting point.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
function staffAvailability($availability, $staff){ // $staff holds the complete user data // $availability is the current set of rules // Only altering availability for USERID 3 if($staff->data->ID == 3){ $newSet = array(); try { // add the private ics file here $ical = new ICal('URL TO PRIVATE ICS FILE', array( 'defaultSpan' => 2, // Default value 'defaultTimeZone' => 'Europe/Berlin', 'defaultWeekStart' => 'MO', // Default value 'disableCharacterReplacement' => false, // Default value 'skipRecurrence' => false, // Default value 'useTimeZoneWithRRules' => false, // Default value )); } catch (\Exception $e) { die($e); } $forceTimeZone = true; $events = $ical->sortEventsWithOrder($ical->events()); // looping through all events foreach ($events as $event) { // Get Start and end date / time information $dtstart = $ical->iCalDateToDateTime($event->dtstart_array[3], $forceTimeZone); $dtend = $ical->iCalDateToDateTime($event->dtend_array[3], $forceTimeZone); // Define new time:range rule // Adding one rule per day // Added Friday to the calendar + event recurring 4 times, which results in 4 new rules for staff member 3 $newSet[] = array( 'type' => "time:range",// rule type used for this example 'appointable' => "yes", '[priority' => 10, 'from' => $dtstart->format('H:i'), // start time 10:00 'to' => $dtend->format('H:i'), // end time 16:00 'from_date' => $dtstart->format('Y-m-d'), // start date - Friday 'to_date' => $dtend->format('Y-m-d') // end date - Friday ); $availability = $newSet; } } return $availability; } |
The example pulls and parses the ics file on every load, use a transient or REDIS to store data and only refresh in certain intervals.
Hope this gets you started! I build a simple interface around it, with a lot of more rule options. This makes the setup for each staff member a brise. Now each of them can setup a calendar easily and provide me with the ics link :) WooCommerce Appointments rocks …
There are many variations of these out there, SSilence/php-imap-client is a lib with a nice set of methods, clean integration and pretty good documentation.
Adding it to my goto essentials !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$mailbox = 'my.imapserver.com'; $username = 'username'; $password = 'secret'; $encryption = Imap::ENCRYPT_SSL; // Open connection try{ $imap = new Imap($mailbox, $username, $password, $encryption); // You can also check out example-connect.php for more connection options }catch (ImapClientException $error){ echo $error->getMessage().PHP_EOL; die(); // Oh no :( we failed } // Get all folders as array of strings $folders = $imap->getFolders(); foreach($folders as $folder) { echo $folder; } // Select the folder Inbox $imap->selectFolder('INBOX'); // Count the messages in current folder $overallMessages = $imap->countMessages(); $unreadMessages = $imap->countUnreadMessages(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
ALL All messages in the mailbox; the default initial key for ANDing. ANSWERED Messages with the \Answered flag set. BCC Messages that contain the specified string in the envelope structure's BCC field. BEFORE Messages whose internal date (disregarding time and timezone) is earlier than the specified date. BODY Messages that contain the specified string in the body of the message. CC Messages that contain the specified string in the envelope structure's CC field. DELETED Messages with the \Deleted flag set. DRAFT Messages with the \Draft flag set. FLAGGED Messages with the \Flagged flag set. FROM Messages that contain the specified string in the envelope structure's FROM field. HEADER Messages that have a header with the specified field-name (as defined in [RFC-2822]) and that contains the specified string in the text of the header (what comes after the colon). If the string to search is zero-length, this matches all messages that have a header line with the specified field-name regardless of the contents. KEYWORD Messages with the specified keyword flag set. LARGER Messages with an [RFC-2822] size larger than the specified number of octets. NEW Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to "(RECENT UNSEEN)". NOT Messages that do not match the specified search key. OLD Messages that do not have the \Recent flag set. This is functionally equivalent to "NOT RECENT" (as opposed to "NOT NEW"). ON Messages whose internal date (disregarding time and timezone) is within the specified date. OR Messages that match either search key. RECENT Messages that have the \Recent flag set. SEEN Messages that have the \Seen flag set. SENTBEFORE Messages whose [RFC-2822] Date: header (disregarding time and timezone) is earlier than the specified date. SENTON Messages whose [RFC-2822] Date: header (disregarding time and timezone) is within the specified date. SENTSINCE Messages whose [RFC-2822] Date: header (disregarding time and timezone) is within or later than the specified date. SINCE Messages whose internal date (disregarding time and timezone) is within or later than the specified date. SMALLER Messages with an [RFC-2822] size smaller than the specified number of octets. SUBJECT Messages that contain the specified string in the envelope structure's SUBJECT field. TEXT Messages that contain the specified string in the header or body of the message. TO Messages that contain the specified string in the envelope structure's TO field. UID Messages with unique identifiers corresponding to the specified unique identifier set. Sequence set ranges are permitted. UNANSWERED Messages that do not have the \Answered flag set. UNDELETED Messages that do not have the \Deleted flag set. UNDRAFT Messages that do not have the \Draft flag set. UNFLAGGED Messages that do not have the \Flagged flag set. UNKEYWORD Messages that do not have the specified keyword flag set. UNSEEN Messages that do not have the \Seen flag set. |
We all have been in situations were we need content or information from a connected website, but have no access to a REST Api or any other backend feed.
In these cases screen scraping is the only option to get needed information to finalize an integration. You can do that directly in CURL, but that can be tedious. Far easier to use a nicely packaged solution that combines a component that simulates web browser behavior and a component that eases DOM navigation for HTML and XML documents. Meet Goutte!
Install via composer.
1 |
composer require fabpot/goutte |
Login into a website and navigate to the page that has your needed information
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$client = new Client(); $crawler = $client->request('GET', 'https://www.page/login.php'); // select the form and fill in some values $form = $crawler->selectButton('Login')->form(); $form['f_loginname'] = 'HelloMe'; $form['f_loginpass'] = 'securepass'; // submit that form $crawler = $client->submit($form); // go to next page $crawler = $client->request('GET', 'https://www.page.de/overview.php'); |
Get the data you need.
1 2 3 4 5 6 7 8 |
// loop over html and filter out what you need $crawler->filter('table.clients tr')->each(function ($node) { $node->filter('td')->each(function ($sub_node) { echo $sub_node->html(); } } |
Goutte @ Github
BrowserKit Documentation
DOM Crawler Documentation
QueryBuilder is an UI component to create queries and filters.
Really neatly programmed solution, with many possible ways to hook into it with your own plugins.
An icon font to use with the Google Maps API and Google Places API, using SVG markers and icon labels.
Map Icons makes Google Maps Markers dynamic with control over shape, color, size, and icon easily changed using options in the marker object as well as simple SVG Notation and CSS.
I have been working on a project , that relies on text-to-speech cloud services for a lot of things.
Audiomanager abstracts access to popular text-to-speech cloud services like Google, Ivona, Amazon Polly… Really a timesaver, when evaluating different services.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$adapter = new \AudioManager\Adapter\Polly(); $adapter->getOptions()->initialize() ->setVersion('latest') ->setRegion('us-west-2') ->setCredentials() ->setKey('...') ->setSecret('...'); $adapter->getOptions()->setOutputFormat('...'); //Default 'mp3' $adapter->getOptions()->setLexiconNames('...'); $adapter->getOptions()->setSampleRate('...'); //Default '16000' $adapter->getOptions()->setTextType('...'); //Default 'text' $adapter->getOptions()->setVoiceId('...'); //Default 'Salli' |