BookStack is an open-source, web-based platform for organizing, storing, and sharing knowledge and documentation. It was developed to provide a user-friendly and intuitive interface for creating and managing knowledge bases, wikis, and other types of structured content.
BookStack allows users to create books, chapters, and pages, and to organize them hierarchically. Users can also add images, files, and links to their content, and collaborate with others by assigning roles and permissions. BookStack also supports full-text search, version control, and commenting, making it easy to find and update information.
BookStack is written in PHP and uses the Laravel framework, and it is available for free under the MIT License.
This is my internal homelab documentation system, I use it for private and client related content. Bookstack is fast, clean and well documented. It has really elevated the way I store and access important reminders / how-tos and documentation.
The system provides API access and has many ways to tweak it. Bookstack documentation.
CSV stands for „Comma-Separated Values“. It is a simple file format used to store tabular data, such as spreadsheets or databases, in plain text. In a CSV file, each line represents a row of data, and each column is separated by a comma (or sometimes a semicolon or tab).
There are always CSV exports or other data stored in CSV format, that i need quick access to sometimes.
Bookstack allows to add files to pages and insert links to them, but it does not embed or parse those files in any way. There are similar hacks to mine, this example allows to embed PDFs and here the main Hacks page.
We need something to parse the CSV file and something to display the information in a nice visual & flexible grid.
Included and linked CSV files look something like that in source.
1 2 3 |
<p id="bkmrk-samplecsvfile_2kb.cs"> <a href="https://bookstack.instance/attachments/12" target="_blank" rel="noopener">SampleCSVFile_2kb.csv</a> </p> |
The settings allow you to add custom code to your instance. Another option would be to tweak template files, but its easier to do these light tweaks using the header customizer.
This can be found under: https://bookstack.instance/settings/customization
Either link them externally or better use locally stored versions of these.
1 2 3 |
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/papaparse@5.4.0/papaparse.min.js"></script> <script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script> <link href="https://cdn.jsdelivr.net/npm/gridjs/dist/theme/mermaid.min.css" rel="stylesheet" /> |
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 |
<script> function init() { document.querySelectorAll('.page-content a').forEach((node, i) => { let parent = node; let unique = i; let text = node.textContent let link = node.href if (text.search(".csv") !== -1) { let header; let rows = []; let json = JSON.parse(node.getAttribute("title")); Papa.parse(link, { download: true, header: false, complete: function(results) { results.data.map(function(data, index) { if(!!json){ let removeValFrom = json.remove_cols; data = data.filter(function(value, index) { return removeValFrom.indexOf(index) == -1; }) } if (index == 0) { header = data; } else { rows.push(data); } }); const csv_wrapper = document.createElement("div"); csv_wrapper.setAttribute("id", "csv_wrapper_" + unique_id); parent.after(csv_wrapper); new gridjs.Grid({ search: true, columns: header, data: rows, fixedHeader: true, sort: true, pagination: { limit: 6 } }).render(document.getElementById("csv_wrapper_" + unique_id)); } }); } }); } window.addEventListener('DOMContentLoaded', init); </script> |
Github: Will be adding more hacks, as I clean some of them up :)
You can use the title attribute to pass some config data, in JSON format. Currently only to strip columns, but will extend that a bit more for flexible usage ;)
1 |
{"remove_cols":[0,1,2,3,6,7,8,9,10]} |
Should look something like that …
Enjoy coding …
In SEO (Search Engine Optimization), there is a concept called „E-A-T“ which stands for „Expertise, Authoritativeness, and Trustworthiness“.
Google uses E-A-T as one of its many ranking factors to evaluate the quality of content on the web. Websites that consistently produce high-quality content that meets the E-A-T criteria are more likely to rank well in search engine results pages (SERPs).
E-A-T is not a specific algorithm or ranking factor that Google uses, but rather a framework or set of guidelines that Google’s quality raters use to evaluate the quality of content on the web. These quality raters assess the content using E-A-T as a benchmark, and their feedback helps Google improve its search algorithms.
It is especially important for content that falls under the category of YMYL (Your Money or Your Life), which includes content related to health, finance, legal, and other topics that can impact people’s well-being or financial stability. Google holds this type of content to a higher standard because inaccurate or misleading information can have serious consequences.
Building E-A-T for your website or content involves a multi-faceted approach that includes creating high-quality, informative, and engaging content, establishing your expertise and authority in your field, and building trust with your audience through transparency and honesty.
Some specific actions you can take to improve your website’s E-A-T include showcasing the expertise of your content creators, publishing authoritative and accurate content, providing clear and transparent information about your business, and building a positive online reputation through reviews, testimonials, and other forms of social proof.
It’s important to note that E-A-T is just one of many factors that Google uses to determine search rankings, and it’s not the only factor. Other factors that can influence search rankings include content relevance, website speed and performance, user experience, and backlinks.
Just another E, for Experience. 2023 Google wants to see that a content creator has first-hand, real-world experience with the topic discussed. Which means that content and there creators are becoming far more entwined. Further pushing the trend for authoritative quality content for audiences.
So back to a solid author bios, detailed author pages and all relevant links that detail an authors expertise. Thank you! Search is really shifting and things are changing rapidly. Real content is queen or king again :)
Enjoy coding …
Productivity tools are software applications that help individuals and teams to manage their time and tasks more effectively. These tools can range from simple time tracking and task management apps to more advanced project management software.
The goal of productivity tools is to increase efficiency, organization, and accountability, allowing users to accomplish more in less time. They can be used for a variety of purposes, such as scheduling meetings, setting reminders, tracking progress, and delegating tasks. With the right productivity tools, individuals and teams can streamline their workflows, increase their productivity, and achieve their goals more easily.
I always use the first week of the year to evaluate my tools, workflows and software. I try to switch as little as possible within every 6 months, as I can not afford any downtime during my projects. This is the first year, that I am sharing a mostly complete list of things I use. Might be interesting for some of you.
Update: Not completely done yet, but close :)
Pretty flexible, when it comes to these tools. I am working with different agencies and customers, some prefer Adobe XD and some Figma.
I am a Mac and Linux guy. Windows only exists virtualised in my work environment ;)
Again trying to free myself from the Adobe subscription model. Might be the last year and than I can finally transition away ;)
Love to switch it up and again be flexible when it comes to customer preferences ;)
I am always staying busy, when it comes to programming languages. Have my side projects, that keep me exploring new stuff. My current top 8 ;)
„A homelab is a personal laboratory or workspace that is set up in a person’s home, typically for the purpose of experimenting with and learning about various technology or IT related topics. This can include things like building and configuring servers, experimenting with different operating systems and software, and learning about network and security concepts.“
I started my Homelab 2020 to allow more flexible workflows, to play with Docker setups, lower costs for external services and have full access to any server tool when ever needed. Allows me to quickly mirror customer setups or simulate possible upgrade paths. Quite addictive once you start building your own server. And the things you learn, can be easily replicated to remote servers, which I just did last year. I am starting to move services from a long-time static server to a scalable docker based foundation.
Will follow up on this section soon ….
Enjoy coding ….
jQuery has provided easy access to complicated core Javascript solutions in the past and has been shielding us from difficult workarounds for legacy browsers. But times have changed and many of those things can be done as easily using Javascript directly.
jQuery is a fast, small, and feature-rich JavaScript library. It makes interactions with HTML documents easy, and is widely used in web development to add features to web pages and to simplify the process of writing JavaScript. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript.
That being said, jQuery is still a popular and widely used library, and there are many valid reasons to continue using it. It is ultimately up to you to decide whether the benefits of using jQuery outweigh the potential drawbacks in your particular situation.
Easily search and compare direct Javascript solutions to jQuery ….
How jQuery does it:
1 |
$.getJSON('/my/url', function (data) {}); |
Pure Javascript:
1 2 |
const response = await fetch('/my/url'); const data = await response.json(); |
UmbrellaJS is a lightweight JavaScript library that provides a number of utility functions and features for working with DOM elements and handling events. It was designed to be small, fast, and easy to use, and it does not have any dependencies on other libraries.
Some of the features provided by UmbrellaJS include:
UmbrellaJS is a good choice for developers who want a simple, lightweight library for working with DOM elements and handling events. It is especially well-suited for smaller projects or for developers who want to avoid the overhead of larger libraries like jQuery.
Selector demos:
1 2 3 4 5 6 7 |
u('ul#demo li') u(document.getElementById('demo')) u(document.getElementsByClassName('demo')) u([ document.getElementById('demo') ]) u( u('ul li') ) u('<a>') u('li', context) |
Documentation
Migrate from jQuery
HTMX (HTML enhanced for asynchronous communication and XML) is a JavaScript library that allows you to add asynchronous communication and other interactive features to your web pages using HTML attributes and elements. It allows you to make AJAX (Asynchronous JavaScript and XML) requests and handle responses directly in your HTML, without the need for writing any JavaScript code.
HTMX works by intercepting events on HTML elements and making asynchronous requests based on the attributes you specify. For example, you can use the hx-get
attribute to make a GET request to a specified URL, and use the hx-trigger
attribute to specify an event that should trigger the request. You can also use the hx-target
attribute to specify an element on the page where the response should be inserted.
Here’s an example of how you might use HTMX to make a GET request and insert the response into a div
element:
1 2 3 |
<button hx-get="/some/url" hx-target="#target">Load Data</button> <div id="target"></div> |
When the button is clicked, HTMX will make a GET request to /some/url
and insert the response into the div
element with the id
of target
.
HTMX is designed to be easy to use and flexible, and it can be used to add a wide range of interactive features to your web pages.
I am currently using HTMX in one of my longterm projects and will be talking about it more in a separate article in the future !
HTMX Reference / Documentation
It is ultimately up to you to decide which approach is best for your particular project. Sometimes a combination is needed ;)
A fullstack developer is a software engineer who has expertise in all layers of a web application’s stack. This includes both the frontend, which is the user-facing part of the application, and the backend, which is the server-side portion of the application.
To become a fullstack developer, one must have a solid understanding of a wide range of technologies. This includes proficiency in at least one programming language, such as JavaScript or Python, as well as knowledge of databases, server infrastructure, and web development frameworks.
One of the key benefits of being a fullstack developer is that they can work on any part of a web application, from the design and user experience to the underlying server-side logic. This means that they can take on a wide range of roles and responsibilities, from designing the user interface to implementing complex business logic.
Fullstack developers are also in high demand, as the skills they possess are highly sought-after in the job market. This is because companies are increasingly looking for engineers who can work on both the frontend and backend of their web applications, rather than hiring separate teams for each layer of the stack.
In addition to their technical skills, fullstack developers must also have strong problem-solving and communication skills. This is because they often work on teams and need to be able to collaborate effectively with other developers, as well as communicate their ideas to non-technical stakeholders.
Overall, being a fullstack developer is a challenging but rewarding career path. It requires a diverse set of skills and the ability to adapt to new technologies, but the rewards include the opportunity to work on a wide range of projects and the satisfaction of seeing your work come to life in the form of a web application.
A while back a potential customer asked me, if it is possible to restructure a WordPress Multisite setup and WPML with a more simplified and custom url structure?
web.site/de/
web.site/en/
Languages would normally be added like this:
web.site/nl-nl/de/
web.site/nl-nl/en/
The customer wanted it to be restructured / simplified like this:
web.site/de-nl/
web.site/en-nl/
This basically mimics the structure of a single WPML website with custom languages, but with all the benefits of a multisite.
This is nothing that WPML or WordPress Multisite provides out of the box.
I built a prototype setup to make it work.
Not something that I would propose for anyone, as it requires a lot of tweaks for anything that handles dynamic links (plugins, hooks, core systems, page.builder …)
Its doable :)
One thing that needs to be tweaked globally, is the mapping of the new url structure.
So web.site/nl-nl/en/ needs to become web.site/en-nl/
This needs to be handled on the server side, by proxying the original to the new structure.
This can be easily done using Apache or NGINX.
With that web.site/nl-nl/en/ will be proxied to web.site/en-nl/, but any core navigation will not work yet.
This is the fastest solution that I came up with, within the hour I gave myself ;)
There surely are other options, like the core rewrites / restructuring of the core shorturl handling. But these approaches might break things in far more areas.
Using the proxy approach, keeps the core as it is. The solution needs to be as simple as possible, allowing to maintain it in the future :)
Just for the basic setup a couple of hooks are required to make this work, more might be needed depending on the plugins in use.
Here a couple of examples ….
WordPress site_url
1 2 3 4 5 6 7 8 9 |
add_filter( 'site_url', 'custom_site_url' ); function custom_site_url( $url ) { if( is_admin() ) // you probably don't want this in admin side return $url; return str_replace( "/nl-nl/en/","/en-nl/", $url); } |
WordPress Nav Links
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
add_filter( 'post_link', 'changePermalinks', 10, 3); add_filter( 'page_link', 'changePermalinks', 10, 3); add_filter( 'post_type_link', 'changePermalinks', 10, 3); add_filter( 'category_link', 'changePermalinks', 11, 3); add_filter( 'tag_link', 'changePermalinks', 10, 3); add_filter( 'author_link', 'changePermalinks', 11, 3); add_filter( 'day_link', 'changePermalinks', 11, 3); add_filter( 'month_link','changePermalinks', 11, 3); add_filter( 'year_link', 'changePermalinks', 11, 3); function changePermalinks( $url, $post, $leavename=false ) { $url = str_replace("/nl-nl/en/","/en-nl/", $url); return $url; } |
WPML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
add_filter( 'icl_ls_languages', 'wpml_url_fix'); function wpml_url_fix( $languages ) { global $wpml_url_converter; $abs_home = $wpml_url_converter->get_abs_home(); foreach( $languages as $lang => $element ){ $languages[$lang]['url'] = str_replace( "/nl-nl/en/","/en-nl/", $languages[$lang]['url'] ); } return $languages; } |
Rankmath
1 2 3 |
add_filter( 'rank_math/frontend/canonical', function( $canonical ) { return str_replace( "/nl-nl/en/","/en-nl/", $canonical); }); |
This will not cover every angle, but will give you a starting point! I love my puzzles and there always is a viable solution :)
Need something similar … get in touch!
When you look at Youtube, Twitter, certain Facebook groups and even some software companies, they are all building up fear for the upcoming / in-progress Google „Helpful Content“ algorithm update.
– Our “helpful content update” launching next week will better surface original, helpful content made by people, for people, rather than content made primarily to gain search traffic. It’s part of a broad effort to show more unique, authentic info in results – Google SearchLiaison@Twitter
Its happening as I write this and its about time!
The goal of this update is to rank websites that publish original and unique content. Content written by real writers and not AIs.
This also downgrades websites, that write about content that is not relevant to their core expertise. So no more content domain dominance, by posting about every possible content angle to lure visitors in.
Also old content, when not updated regularly, will loose prominence.
This all is a plus for the enduser and knowledge seeker. Google has been preparing for this for years now and its not happening suddenly.
Structured data gained more and more importance over the past few years. Google is finally using it to cleanup search!
Hard to tell. But change was needed! There are so many underrated websites out there, that deliver quality content, but never got a chance to bubble up or shine :) This will hopefully get us better search results and better quality control.
LOOKING FORWARD TO THE RESULTS!
Keep on breathing …. ALEX
Ich hatte dieses Jahr die Möglichkeit, über meinen Kunden TYPEMYKNIFE®, an der „Nacht der Sterne“ in Stuttgart, im Mercedes-Benz Museum, teilzunehmen. Auf der Gala kommen mehr als 800 Gäste aus Gastronomie, Hotellerie, Politik, Kultur und Wirtschaft zusammen.
Es war ein klasse Abend, auf dem nicht nur die Spitzenköche aus Deutschland, der Schweiz, Südtirol und Österreich ausgezeichnet wurden, sondern diese auch Live zeigen konnten was sie so können.
Veranstaltet wird die Party von der Allgemeinen Hotel- und Gastronomie-Zeitung (Ahgz) und Burg Staufeneck / Rolf Straubinger.
Moderatorin des Abends waren die Journalistin und Fernsehköchin Felicitas Then und Rolf Westermann von der ahgz-Chefredaktion.
Informationen zum Award, den Methoden und Siegern findet man hier.
TYPEMYKNIFE® hat Vorort an einem Stand eine kleine Auswahl seiner Küchenmesser, die über den 3D Gravur Konfigurator vorbereitet und graviert wurden, präsentiert. Dadurch hatten Gäste die Möglichkeit, die gravierten Küchenmesser einmal persönlich zu entdecken und die Qualität zu bestaunen.
Die fast 1400 km Rundreise aus dem Norden hat sich gelohnt. Es ist immer schön Kunden mal nicht nur virtuell zu treffen, besonders wenn die Distanz so groß ist. Bei der Entfernung trifft man sich nicht immer mal kurz auf einen Kaffee oder Gin-Tonic :)
Gruß an TYPEMYKNIFE® / Schwäbisch Gmünd / Stuttgart
Color correction is important to me and to the projects I do. Regularly calibrating your monitor is essential in order to create prints that accurately represents what you see on your monitor.
I have color-profiles (ICC – International Color Consortium) for different light conditions and I do recalibrate every few months, as the light output and colors degrade on my displays.
I am still using an old Spyder3 from datacolor for calibration, which is not really supported anymore. Hard to find working software for my Windows 10 machines and Mac M1.
To the rescue comes DisplayCAL, an Open Source calibration solution that allows you to calibrate your displays using even older calibration hardware.
ArgyllCMS – is an ICC compatible color management system, available as Open Source. This is requested by DisplayCAL to make the magic work. ( MAC M1 – Fork)
I have calibrated all machines with this and love it :)
This is not a tutorial, but more like sharing a nice geeky road-trip ;)
I have a pretty good understanding of the Youtube Data API, as I have actively used it on portalZINE TV in the past, to upload videos and dynamically link them to my local post-types.
For one of my latest customer projects (TYPEMYKNIFE / typemyknife.com), the task was a bit more complicated and the goal was to make it as future-proof as it can be with the Google APIs :)
Prerequisites / References to get you started:
The goal for the setup was to actively synchronize WooCommerce products with linked / attached videos, with their source at Youtube.
As the website is multilingual, WPML integration is critical as well. And as Youtube allows localization of title and description, that can be added into the mix quiet easily in the future ;)
The following product attributes should be mirrored and optimised for Youtube:
The following attributes should be integrated into the description to enrich the Youtube description:
All of these attributes will be collected internally and assigned using a simple template system, which allows the customer to move parts around freely and freely layout the description for Youtube.
The following stats will be collected for review:
Youtube SEO
These are the relevant key aspects, that help to get your videos more views.
In the past access to the Youtube Data API was far easier and less limited, when it comes to offline / none expiring OAuth2 refresh tokens.
When you are building a server-side application that is only available to your customer or moderators, it makes no sense to run that app through the Google App verification. Your app will never be used in public.
The Youtube Data API and its scopes, are defined as sensitive and therefor require third-party security assessment for public access.
The scopes I am requesting are https://www.googleapis.com/auth/youtube.upload + https://www.googleapis.com/auth/youtube.
Because of that its far easier to just setup OAuth 2 in test mode and restrict access to your customer and specific additional accounts only (up to 100 test users allowed). What all these account need, is access to your own or Brand Youtube Channel.
Preparation in the Google Cloud Console:
A detailed description can be found here.
You can circumvent verification for the consent screen, by using an organisation setup at Google. Here some infos about that. With that setup offline refresh tokens should work fine.
Update: Just tried that, but wont work with a branded youtube account, even though the cloud user has admin access to it. Not giving up yet, but Google / Youtube really makes it difficult to just have a simple offline solution for specific tasks ;) BTW also forced the login hint, to make sure the right account is logged in : $client->setLoginHint(‚YourWoreksapceAccount‘); !
You might have heard of the „The League of Extraordinary Packages„. It is a group of developers who have banded together to build solid, well tested PHP packages using modern coding standards.
They also offer an OAuth2-client + OAuth2 Google extension that can be used.
On the server, the Google API PHP SDK can be easily integrated using Composer.
In my customer plugin I neatly separated all relevant areas in classes & traits:
You can check the expiry time of your access token by accessing:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=YOUR_TOKEN„A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of „Testing“ is issued a refresh token expiring in 7 days.“ – Google
Basic Auth example from the SDK:
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 |
<?php // Call set_include_path() as needed to point to your client library. set_include_path($_SERVER['DOCUMENT_ROOT'] . '/directory/to/google/api/'); require_once 'Google/Client.php'; require_once 'Google/Service/YouTube.php'; session_start(); /* * You can acquire an OAuth 2.0 client ID and client secret from the * {{ Google Cloud Console }} <{{ https://cloud.google.com/console }}> * For more information about using OAuth 2.0 to access Google APIs, please see: * <https://developers.google.com/youtube/v3/guides/authentication> * Please ensure that you have enabled the YouTube Data API for your project. */ $OAUTH2_CLIENT_ID = 'XXXXXXX.apps.googleusercontent.com'; $OAUTH2_CLIENT_SECRET = 'XXXXXXXXXX'; $REDIRECT = 'http://localhost/oauth2callback.php'; $APPNAME = "XXXXXXXXX"; $client = new Google_Client(); $client->setClientId($OAUTH2_CLIENT_ID); $client->setClientSecret($OAUTH2_CLIENT_SECRET); $client->setScopes('https://www.googleapis.com/auth/youtube'); $client->setRedirectUri($REDIRECT); $client->setApplicationName($APPNAME); $client->setAccessType('offline'); // Define an object that will be used to make all API requests. $youtube = new Google_Service_YouTube($client); if (isset($_GET['code'])) { if (strval($_SESSION['state']) !== strval($_GET['state'])) { die('The session state did not match.'); } $client->authenticate($_GET['code']); $_SESSION['token'] = $client->getAccessToken(); } if (isset($_SESSION['token'])) { $client->setAccessToken($_SESSION['token']); echo '<code>' . $_SESSION['token'] . '</code>'; } // Check to ensure that the access token was successfully acquired. if ($client->getAccessToken()) { try { // Call the channels.list method to retrieve information about the // currently authenticated user's channel. $channelsResponse = $youtube->channels->listChannels('contentDetails', array( 'mine' => 'true', )); $htmlBody = ''; foreach ($channelsResponse['items'] as $channel) { // Extract the unique playlist ID that identifies the list of videos // uploaded to the channel, and then call the playlistItems.list method // to retrieve that list. $uploadsListId = $channel['contentDetails']['relatedPlaylists']['uploads']; $playlistItemsResponse = $youtube->playlistItems->listPlaylistItems('snippet', array( 'playlistId' => $uploadsListId, 'maxResults' => 50 )); $htmlBody .= "<h3>Videos in list $uploadsListId</h3><ul>"; foreach ($playlistItemsResponse['items'] as $playlistItem) { $htmlBody .= sprintf('<li>%s (%s)</li>', $playlistItem['snippet']['title'], $playlistItem['snippet']['resourceId']['videoId']); } $htmlBody .= '</ul>'; } } catch (Google_ServiceException $e) { $htmlBody .= sprintf('<p>A service error occurred: <code>%s</code></p>', htmlspecialchars($e->getMessage())); } catch (Google_Exception $e) { $htmlBody .= sprintf('<p>An client error occurred: <code>%s</code></p>', htmlspecialchars($e->getMessage())); } $_SESSION['token'] = $client->getAccessToken(); } else { $state = mt_rand(); $client->setState($state); $_SESSION['state'] = $state; $authUrl = $client->createAuthUrl(); $htmlBody = <<<END <h3>Authorization Required</h3> <p>You need to <a href="$authUrl">authorise access</a> before proceeding.<p> END; } ?> <!doctype html> <html> <head> <title>My Uploads</title> </head> <body> <?php echo $htmlBody?> </body> </html> |
A simple upload example can be found here .
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 |
try{ // REPLACE this value with the video ID of the video being updated. $videoId = "VIDEO_ID"; // Call the API's videos.list method to retrieve the video resource. $listResponse = $youtube->videos->listVideos("snippet", array('id' => $videoId)); // If $listResponse is empty, the specified video was not found. if (empty($listResponse)) { $htmlBody .= sprintf('<h3>Can\'t find a video with video id: %s</h3>', $videoId); } else { // Since the request specified a video ID, the response only // contains one video resource. $video = $listResponse[0]; $videoSnippet = $video['snippet']; $tags = $videoSnippet['tags']; // Preserve any tags already associated with the video. If the video does // not have any tags, create a new list. Replace the values "tag1" and // "tag2" with the new tags you want to associate with the video. if (is_null($tags)) { $tags = array("tag1", "tag2"); } else { array_push($tags, "tag1", "tag2"); } // Set the tags array for the video snippet $videoSnippet['tags'] = $tags; // Update the video resource by calling the videos.update() method. $updateResponse = $youtube->videos->update("snippet", $video); $responseTags = $updateResponse['snippet']['tags']; $htmlBody .= "<h3>Video Updated</h3><ul>"; $htmlBody .= sprintf('<li>Tags "%s" and "%s" added for video %s (%s) </li>', array_pop($responseTags), array_pop($responseTags), $videoId, $video['snippet']['title']); $htmlBody .= '</ul>'; } } catch (Google_Service_Exception $e) { $htmlBody .= sprintf('<p>A service error occurred: <code>%s</code></p>', htmlspecialchars($e->getMessage())); } catch (Google_Exception $e) { $htmlBody .= sprintf('<p>An client error occurred: <code>%s</code></p>', htmlspecialchars($e->getMessage())); } |
All operations to and from the Youtube Data API are rate limited. What is important for us, are the queries per day.
The default quota is 10.000 queries per day, sounds a lot, but is easily gone after updating 150-200 videos. You can request this limit to be raised, but again a lot of paperwork and questions that are just not needed.
The above limit just means, that you need to cache as many queries as possible, to only query live when needed ;)
Something you learn fast, when experimenting with different things! I hit that limit multiple times in the first few days, with around 500 videos in the queue.
Different operation cost you different amount of units
It also helps to use the Google Developer Playground to testdrive the Youtube Data API with your own credentials while optimising your own code.
You can define your own OAuth 2.0 configuration by clicking the cog in the upper right corner.
I setup the bulk updating to allow splitting it over multiple days, if required. For this an offline refresh token is needed, as the standard token expires after 60 minutes.
My customer can also just update a single video, when changes are applied to the product or a new product has been added.
If more frequent updates are required, I will ask for a raise of the queries per day. You can circumvent the limit by using multiple Google Cloud Platform accounts with new OAuth credentials, but really an overkill right now. I have done that in the past ;)
The GUI is just based of Bootstrap, to make it simple and clean. Using my own wrapper to make it work within the WordPress admin.
For all ajax operations, I am using htmx and _hyperscript, which I will talk about in another article in the future.
Really neat and clean way to build single page interfaces.
The whole plugin runs of its own REST API endpoint. Just love using WordPress as a headless system.
I used TWIG / Timber for the templates, to separate logic and layout. Timber has been my goto solution for years now. It drives my own and many customer websites.
This has been a lot of fun, maybe a bit too much LOL
I do geek-out about many of my projects, but this experience helped me to bring my WordPress toolbox to the next level. This will help to drive other things in the future.
Working so deeply with the Youtube Data API has been fun and feels so easy now, after all remaining problems have been solved.
Would have loved this during my portalZINE TV days ;)
I you read all this, you just earned yourself a badge for completion ;)
Need something similar or something else? Just say hi and we can talk.