Easily 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' |
PHP dotenv loads environment variables from .env
to getenv()
, $_ENV
and $_SERVER
automagically.
You should never store sensitive credentials in your code. Anything that is likely to change between deployment environments – such as database credentials or credentials for 3rd party services – should be extracted from the code into environment variables.
Add your application configuration to a .env file in the root of your project. Make sure the .env file is added to your .gitignore so it is not being checked-in.
1 2 |
S3_BUCKET="dotenv" SECRET_KEY="souper_seekret_key" |
1 2 3 4 5 6 7 |
$dotenv = new Dotenv\Dotenv(__DIR__); $dotenv->load(); // OR WITH filename $dotenv = new Dotenv\Dotenv(__DIR__, 'myconfig'); $dotenv->load(); |
1 2 3 |
$s3_bucket = getenv('S3_BUCKET'); $s3_bucket = $_ENV['S3_BUCKET']; $s3_bucket = $_SERVER['S3_BUCKET']; |
Simple little snippet, that can easily be used in conjunction with wp_update_user()
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 |
/* Generate Unique User Nicename */ function generate_unique_user_nicename( $x ) { // set up args to query $args = array( 'search' => sanitize_title( $x ), 'search_columns' => array( 'user_nicename' ) ); // query for user $user_url_exists = new WP_User_Query( $args ); $results = $user_url_exists->get_results(); // if user url is found, check for new one if( !empty( $results ) ) { $i = substr( sanitize_title( $x ), intval( strrpos( sanitize_title( $x ) ,'-' ) ) + 1 ); if( is_numeric( $i ) ) { $i++; $y = substr($x, 0, strrpos($x,'-' )); $company_name = $y . '-' . $i; } else { $company_name = $x . '-1'; } $new_company_name = sanitize_title( $company_name ); return generate_unique_user_nicename( $new_company_name ); } else { return sanitize_title( $x ); } } |
“Logstalgia is a website traffic visualization tool that replays or streams web-server access logs as a pong-like battle between the web server and an never ending torrent of requests.
Requests appear as colored balls (the same color as the host) which travel across the screen to arrive at the requested location. Successful requests are hit by the paddle while unsuccessful ones (eg 404 – File Not Found) are missed and pass through.
The paths of requests are summarized within the available space by identifying common path prefixes. Related paths are grouped together under headings. For instance, by default paths ending in png, gif or jpg are grouped under the heading Images. Paths that don’t match any of the specified groups are lumped together under a Miscellaneous section.”