{"id":3388,"date":"2017-02-01T10:18:06","date_gmt":"2017-02-01T09:18:06","guid":{"rendered":"https:\/\/en.florianbrinkmann.de\/?p=3388"},"modified":"2020-02-09T10:59:57","modified_gmt":"2020-02-09T09:59:57","slug":"automatic-updates-for-wordpress-themes-which-are-not-in-the-theme-directory","status":"publish","type":"post","link":"https:\/\/florianbrinkmann.com\/en\/automatic-updates-for-wordpress-themes-which-are-not-in-the-theme-directory-3388\/","title":{"rendered":"Automatic updates for WordPress themes which are not in the theme directory"},"content":{"rendered":"\n<p>You can install automatic updates for themes from the WordPress.org directory. Here I show you how you can provide these automatic updates for themes, which are not in the directory.<\/p>\n\n\n\n<!--more-->\n\n\n\n<div class=\"update-box\">\n<p><strong>Update from February 9, 2017<\/strong>: This solution does not work for multisite installations.<\/p>\n<\/div>\n\n\n\n<p>I did not want to dive into the subject of theme updates. My search for a solution for my shop guided me to the plugin <a href=\"https:\/\/woocommerce.com\/products\/woocommerce-api-manager\/\"><em>WooCommerce API Manager<\/em><\/a>, which I used for a short while. From the beginning, I did not like the idea, that the users have to create an account on my site for getting automatic updates. After stumbling over a few problems with child themes and update notifications which appear twice, I searched for the reason in the plugin\u2019s code and WordPress core.<\/p>\n\n\n\n<p>After some time I have not fixed all problems but understood how theme updates are working. So I created my own solution because I did not have many requirements.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Update routine requirements<\/h2>\n\n\n\n<p>I do not need to check in how many installations a bought theme is active because I do not limit the downloads. The following points should be fulfilled:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The solution works with the default download link for digital products from WooCommerce, which is sent to the customer.<\/li><li>To activate the automatic updates, the customer inserts the link into a field in the customizer.<\/li><li>The updates have to work with an active child theme of the paid theme.<\/li><li>Switching to a theme which is neither the paid theme nor its child theme removes the download link.<\/li><\/ul>\n\n\n\n<p>Before we continue with the solution, here comes a summary of how theme updates work in WordPress.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Procedure of theme updates in WordPress<\/h2>\n\n\n\n<p>It is important for us that there is a transient <code class=\"lang-php\">update_themes<\/code> which stores information about the installed themes and available updates. The transient\u2019s content can look something like that when all themes are up to date:<\/p>\n\n\n<pre class=\"wp-block-code lang-markup\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">object(stdClass)<span class=\"hljs-comment\">#273 (4) {<\/span>\n    &#91;<span class=\"hljs-string\">\"last_checked\"<\/span>]=&gt;int(<span class=\"hljs-number\">1485868202<\/span>)\n    &#91;<span class=\"hljs-string\">\"checked\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">7<\/span>) {\n        &#91;<span class=\"hljs-string\">\"extant\"<\/span>]=&gt;string(<span class=\"hljs-number\">5<\/span>) <span class=\"hljs-string\">\"1.0.1\"<\/span>\n        &#91;<span class=\"hljs-string\">\"rindby\"<\/span>]=&gt;string(<span class=\"hljs-number\">5<\/span>) <span class=\"hljs-string\">\"1.1.3\"<\/span>\n        &#91;<span class=\"hljs-string\">\"schlicht-child\/schlicht-child\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.0\"<\/span>\n        &#91;<span class=\"hljs-string\">\"schlicht\"<\/span>]=&gt;string(<span class=\"hljs-number\">5<\/span>) <span class=\"hljs-string\">\"1.0.4\"<\/span>\n        &#91;<span class=\"hljs-string\">\"twentyfifteen\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.7\"<\/span>\n        &#91;<span class=\"hljs-string\">\"twentyfourteen\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.9\"<\/span>\n        &#91;<span class=\"hljs-string\">\"twentysixteen\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.3\"<\/span>\n    }\n    &#91;<span class=\"hljs-string\">\"response\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">0<\/span>) {\n    }\n    &#91;<span class=\"hljs-string\">\"translations\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">0<\/span>) {\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If we decrease the version of <em>Extant<\/em> manually, we get the following output (and an update notification in the backend):<\/p>\n\n\n<pre class=\"wp-block-code lang-markup\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">object(stdClass)<span class=\"hljs-comment\">#273 (4) {<\/span>\n    &#91;<span class=\"hljs-string\">\"last_checked\"<\/span>]=&gt;int(<span class=\"hljs-number\">1485868693<\/span>)\n    &#91;<span class=\"hljs-string\">\"checked\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">7<\/span>) {\n        &#91;<span class=\"hljs-string\">\"extant\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.0\"<\/span>\n        &#91;<span class=\"hljs-string\">\"rindby\"<\/span>]=&gt;string(<span class=\"hljs-number\">5<\/span>) <span class=\"hljs-string\">\"1.1.3\"<\/span>\n        &#91;<span class=\"hljs-string\">\"schlicht-child\/schlicht-child\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.0\"<\/span>\n        &#91;<span class=\"hljs-string\">\"schlicht\"<\/span>]=&gt;string(<span class=\"hljs-number\">5<\/span>) <span class=\"hljs-string\">\"1.0.4\"<\/span>\n        &#91;<span class=\"hljs-string\">\"twentyfifteen\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.7\"<\/span>\n        &#91;<span class=\"hljs-string\">\"twentyfourteen\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.9\"<\/span>\n        &#91;<span class=\"hljs-string\">\"twentysixteen\"<\/span>]=&gt;string(<span class=\"hljs-number\">3<\/span>) <span class=\"hljs-string\">\"1.3\"<\/span>\n    }\n    &#91;<span class=\"hljs-string\">\"response\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">1<\/span>) {\n        &#91;<span class=\"hljs-string\">\"extant\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">4<\/span>) {\n            &#91;<span class=\"hljs-string\">\"theme\"<\/span>]=&gt;string(<span class=\"hljs-number\">6<\/span>) <span class=\"hljs-string\">\"extant\"<\/span>\n            &#91;<span class=\"hljs-string\">\"new_version\"<\/span>]=&gt;string(<span class=\"hljs-number\">5<\/span>) <span class=\"hljs-string\">\"1.0.1\"<\/span>\n            &#91;<span class=\"hljs-string\">\"url\"<\/span>]=&gt;string(<span class=\"hljs-number\">36<\/span>) <span class=\"hljs-string\">\"https:\/\/wordpress.org\/themes\/extant\/\"<\/span>\n            &#91;<span class=\"hljs-string\">\"package\"<\/span>]=&gt;string(<span class=\"hljs-number\">54<\/span>) <span class=\"hljs-string\">\"https:\/\/downloads.wordpress.org\/theme\/extant.1.0.1.zip\"<\/span>\n        }\n    }\n    &#91;<span class=\"hljs-string\">\"translations\"<\/span>]=&gt;<span class=\"hljs-keyword\">array<\/span>(<span class=\"hljs-number\">0<\/span>) {\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code class=\"lang-markup\">checked<\/code> version of <code class=\"lang-markup\">extant<\/code> now is <code class=\"lang-markup\">1.0<\/code> \u2013 the theme directory\u2019s version is <code class=\"lang-markup\">1.0.1<\/code>. Interesting is the <code class=\"lang-markup\">response<\/code> part, which stores information about the new version. There is an array with the theme slug as the key. Inside this array, there is another array with the following entries:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code class=\"lang-markup\">theme<\/code> contains the theme slug again. As far as I can say, this is optional.<\/li><li><code class=\"lang-markup\">new_version<\/code> contains the new version number.<\/li><li><code class=\"lang-markup\">url<\/code> is a URL, which is displayed in the overlay after clicking <em>View version 1.0.1 details<\/em> (optional).<\/li><li><code class=\"lang-markup\">package<\/code> is the URL of the ZIP which contains the latest theme version (optional). If this is not set, the user will get an update notification without the update possibility.<\/li><\/ul>\n\n\n\n<p>To notify WordPress about a theme update, you only have to insert the right information into the <code class=\"lang-markup\">response<\/code> array. Everything else is handled by the theme update routine of WordPress core. To modify the transient, there is the filter <code class=\"lang-php\">pre_set_site_transient_update_themes<\/code>, and with that we are ready for the implementation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Implementation of the update script<\/h2>\n\n\n\n<p>Our solution requires the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>A public page with metadata of the latest theme version, so the update script can check if a new version is available. It is necessary to specify at least <code class=\"lang-markup\">new_version<\/code>.<\/li><li>Customizer option for the download link used for <code class=\"lang-markup\">package<\/code>.<\/li><li>A function which is hooked to <code class=\"lang-php\">pre_set_site_transient_update_themes<\/code> which checks for updates and modifies the transient, to kick off the update routine of WordPress.<\/li><li>A function for the <code class=\"lang-php\">switch_theme<\/code> hook to remove the value of the customizer option, if neither the paid theme nor its child theme is active.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Creating metadata page<\/h3>\n\n\n\n<p>It does not matter, how this metadata page is created. You can just upload a static JSON file to your server and update it after releasing a new version. On my site, I have a custom post type for displaying the WordPress themes and their changelogs. The changelog is visible via appending a <code class=\"lang-markup\">changelog\/<\/code> to the single view of a theme (<a href=\"https:\/\/florianbrinkmann.com\/en\/wordpress-themes\/schlicht\/changelog\/\">for example the changelog of Schlicht<\/a>).<\/p>\n\n\n\n<p>So I can get the current version number from this changelog implementation, and created a second sub-page for each theme: <code class=\"lang-markup\">upgrade-json\/<\/code>. <a href=\"https:\/\/florianbrinkmann.com\/en\/wordpress-themes\/schlicht\/upgrade-json\/\">This is the output of Schlicht\u2019s metadata page<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code lang-javascript\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"new_version\"<\/span>: <span class=\"hljs-string\">\"1.0.4\"<\/span>,\n  <span class=\"hljs-attr\">\"url\"<\/span>: <span class=\"hljs-string\">\"https:\/\/florianbrinkmann.com\/en\/wordpress-themes\/schlicht\/changelog\/\"<\/span>,\n  <span class=\"hljs-attr\">\"theme_id\"<\/span>: <span class=\"hljs-number\">2936<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code class=\"lang-markup\">theme_id<\/code> is the ID of the WooCommerce product, which will later be used to check if the URL is a URL to a Schlicht ZIP as good as possible. The update would work with every URL to a valid theme, but the paid theme would be overwritten by the other theme.<\/p>\n\n\n\n<p>This is the function for outputting the JSON code:<\/p>\n\n\n<pre class=\"wp-block-code lang-javascript\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">fbn_theme_upgrade_json<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    $theme_id = get_field( <span class=\"hljs-string\">'woocommerce-product'<\/span> );\n    <span class=\"hljs-keyword\">if<\/span> ( have_rows( <span class=\"hljs-string\">'changelog'<\/span> ) ) {\n        <span class=\"hljs-keyword\">while<\/span> ( have_rows( <span class=\"hljs-string\">'changelog'<\/span> ) ) {\n            the_row();\n            $version       = get_sub_field( <span class=\"hljs-string\">'version'<\/span> );\n            $changelog_url = get_the_permalink() . <span class=\"hljs-string\">'changelog\/'<\/span>;\n            $version_data  = <span class=\"hljs-keyword\">array<\/span>(\n                <span class=\"hljs-string\">'new_version'<\/span> =&gt; $version,\n                <span class=\"hljs-string\">'url'<\/span>         =&gt; $changelog_url,\n                <span class=\"hljs-string\">'theme_id'<\/span>    =&gt; $theme_id,\n            );\n            wp_send_json( $version_data );\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code class=\"lang-php\">wp_send_json()<\/code> creates the JSON output. All other tasks have to be done in the theme which should get updates.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Customizer option for the update URL<\/h3>\n\n\n\n<p>Let us create the customizer option for the update URL first. We insert it into the section <code class=\"lang-php\">schlicht_options<\/code>, which is already created by the theme:<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/**\n * Customizer settings for theme update\n *\n * <span class=\"hljs-doctag\">@param<\/span> WP_Customize_Manager $wp_customize The Customizer object.\n *\/<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">schlicht_update_customize_register<\/span><span class=\"hljs-params\">( $wp_customize )<\/span> <\/span>{\n    $wp_customize-&gt;add_setting( <span class=\"hljs-string\">'schlicht_upgrade_url'<\/span>, <span class=\"hljs-keyword\">array<\/span>(\n        <span class=\"hljs-string\">'type'<\/span>              =&gt; <span class=\"hljs-string\">'option'<\/span>,\n        <span class=\"hljs-string\">'default'<\/span>           =&gt; <span class=\"hljs-string\">''<\/span>,\n        <span class=\"hljs-string\">'sanitize_callback'<\/span> =&gt; <span class=\"hljs-string\">'schlicht_esc_update_url'<\/span>\n    ) );\n\n    $wp_customize-&gt;add_control( <span class=\"hljs-string\">'schlicht_upgrade_url'<\/span>, <span class=\"hljs-keyword\">array<\/span>(\n        <span class=\"hljs-string\">'priority'<\/span> =&gt; <span class=\"hljs-number\">1<\/span>,\n        <span class=\"hljs-string\">'type'<\/span>     =&gt; <span class=\"hljs-string\">'url'<\/span>,\n        <span class=\"hljs-string\">'section'<\/span>  =&gt; <span class=\"hljs-string\">'schlicht_options'<\/span>,\n        <span class=\"hljs-string\">'label'<\/span>    =&gt; __( <span class=\"hljs-string\">'Paste your download link for \u00bbSchlicht\u00ab to enable automatic theme updates.'<\/span>, <span class=\"hljs-string\">'schlicht'<\/span> ),\n    ) );\n}\n\nadd_action( <span class=\"hljs-string\">'customize_register'<\/span>, <span class=\"hljs-string\">'schlicht_update_customize_register'<\/span>, <span class=\"hljs-number\">12<\/span> );<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Important to note is that <code class=\"lang-php\">type<\/code> in the <code class=\"lang-php\">add_setting()<\/code> method is <code class=\"lang-php\">option<\/code>, so this option is not theme specific, and the updates will also work if a child theme of the paid theme is active \u2013 no matter if the URL was inserted with active parent or child theme. We could use <code class=\"lang-php\">esc_url_raw<\/code> as <code class=\"lang-php\">sanitize_callback<\/code>, but I would like to check if the URL starts with <code class=\"lang-markup\">https:\/\/florianbrinkmann.com\/en\/?download_file=<\/code>. That is the beginning of a download URL created by WooCommerce.<\/p>\n\n\n\n<p>The sanitize function looks like that:<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/**\n * Escape URL and check if it matches format https:\/\/florianbrinkmann.com\/en\/?download_file=\n *\n * <span class=\"hljs-doctag\">@param<\/span> $url\n *\n * <span class=\"hljs-doctag\">@return<\/span> string\n *\/<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">schlicht_esc_update_url<\/span><span class=\"hljs-params\">( $url )<\/span> <\/span>{\n    $url     = esc_url_raw( $url );\n    $pattern = <span class=\"hljs-string\">'|^(https:\/\/florianbrinkmann.com\/en\/?download_file=)|'<\/span>;\n    preg_match( $pattern, $url, $matches );\n\n    <span class=\"hljs-keyword\">if<\/span> ( ! <span class=\"hljs-keyword\">empty<\/span> ( $matches ) ) {\n        <span class=\"hljs-keyword\">return<\/span> $url;\n    } <span class=\"hljs-keyword\">else<\/span> {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">''<\/span>;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>First, we run <code class=\"lang-php\">esc_url_raw()<\/code>, and after that, we check if the mentioned pattern matches the URL. If so, we return the URL, otherwise an empty string.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Checking for new updates and modifying the transient<\/h3>\n\n\n\n<p>Now the interesting part. Parts of the following code are inspired by the WooCommerce API Manager plugin:<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/**\n * Checking for updates and updating the transient for theme updates\n *\n * <span class=\"hljs-doctag\">@param<\/span> $transient\n *\n * <span class=\"hljs-doctag\">@return<\/span> mixed\n *\/<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">schlicht_theme_update<\/span><span class=\"hljs-params\">( $transient )<\/span> <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> ( <span class=\"hljs-keyword\">empty<\/span>( $transient-&gt;checked ) ) {\n        <span class=\"hljs-keyword\">return<\/span> $transient;\n    }\n\n    $request = schlicht_fetch_data_of_latest_version();\n    <span class=\"hljs-keyword\">if<\/span> ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) != <span class=\"hljs-number\">200<\/span> ) {\n        <span class=\"hljs-keyword\">return<\/span> $transient;\n    } <span class=\"hljs-keyword\">else<\/span> {\n        $response = wp_remote_retrieve_body( $request );\n    }\n\n    $data     = json_decode( $response );\n    $theme_id = $data-&gt;theme_id;\n    <span class=\"hljs-keyword\">unset<\/span>( $data-&gt;theme_id );\n    <span class=\"hljs-keyword\">if<\/span> ( version_compare( $transient-&gt;checked&#91;<span class=\"hljs-string\">'schlicht'<\/span>], $data-&gt;new_version, <span class=\"hljs-string\">'&lt;'<\/span> ) ) { $transient-&gt;response&#91;<span class=\"hljs-string\">'schlicht'<\/span>] = (<span class=\"hljs-keyword\">array<\/span>) $data;\n\n        $theme_package = get_option( <span class=\"hljs-string\">'schlicht_upgrade_url'<\/span> );\n        <span class=\"hljs-keyword\">if<\/span> ( ! <span class=\"hljs-keyword\">empty<\/span> ( $theme_package ) ) {\n            $pattern = <span class=\"hljs-string\">'|^(https:\/\/florianbrinkmann.com\/en\/?download_file='<\/span> . $theme_id . <span class=\"hljs-string\">')|'<\/span>;\n            preg_match( $pattern, $theme_package, $matches );\n            <span class=\"hljs-keyword\">if<\/span> ( ! <span class=\"hljs-keyword\">empty<\/span> ( $matches ) ) {\n                $transient-&gt;response&#91;<span class=\"hljs-string\">'schlicht'<\/span>]&#91;<span class=\"hljs-string\">'package'<\/span>] = $theme_package;\n            } <span class=\"hljs-keyword\">else<\/span> {\n            }\n        }\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> $transient;\n}\n\nadd_filter( <span class=\"hljs-string\">'pre_set_site_transient_update_themes'<\/span>, <span class=\"hljs-string\">'schlicht_theme_update'<\/span> );<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>After checking if the <code class=\"lang-php\">checked<\/code> entry is empty, we store the result of a <code class=\"lang-php\">wp_safe_remote_get()<\/code> call in <code class=\"lang-php\">$request<\/code>. which is made in&nbsp;<code class=\"lang-php\">schlicht_fetch_data_of_latest_version()<\/code>. This is the <code class=\"lang-php\">schlicht_fetch_data_of_latest_version()<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/**\n * Fetch data of latest theme version\n *\n * <span class=\"hljs-doctag\">@return<\/span> array|WP_Error\n *\/<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">schlicht_fetch_data_of_latest_version<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    $request = wp_safe_remote_get( <span class=\"hljs-string\">'https:\/\/florianbrinkmann.com\/en\/wordpress-themes\/schlicht\/upgrade-json\/'<\/span> );\n\n    <span class=\"hljs-keyword\">return<\/span> $request;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The result is the metadata we prepared above. If everything is all right with the result, we fetch the request\u2019s body with the call of <code class=\"lang-php\">wp_remote_retrieve_body( $request );<\/code> and save it in <code class=\"lang-php\">$response<\/code>. We make it an object with <code class=\"lang-php\">json_decode()<\/code>, save the WooCommerce ID of the theme and remove the ID from the object.<\/p>\n\n\n\n<p>We use <code class=\"lang-php\">version_compare()<\/code> to check if the currently installed version is lower than the version of the fetched metadata. In this case, we save the metadata (<code class=\"lang-markup\">new_version<\/code> and <code class=\"lang-markup\">url<\/code>) as an array in the transient\u2019s <code class=\"lang-markup\">response<\/code> part for the theme. We get the upgrade URL field\u2019s value through <code class=\"lang-php\">get_option( 'schlicht_upgrade_url' );<\/code> and check it against a pattern again. This time, we append the ID of the theme, because the download URL of the WooCommerce product with the ID <code class=\"lang-markup\">123<\/code> would start with <code class=\"lang-markup\">https:\/\/florianbrinkmann.com\/en\/?download_file=123<\/code>. I do not check this in the customizer to reduce <code class=\"lang-php\">wp_safe_remote_get()<\/code> calls.<\/p>\n\n\n\n<p>If the pattern matches, the URL is stored as <code class=\"lang-markup\">package<\/code>, and we return the transient. If it does not match, the user will see an update notification without the possibility to upgrade.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Removing update URL after theme switch<\/h3>\n\n\n\n<p>We use the <code class=\"lang-php\">switch_theme<\/code> action to remove the update URL. We can use <code class=\"lang-php\">wp_get_theme()<\/code> to get an object of the new theme and check its <code class=\"lang-php\">template<\/code> property. This is the same as the theme slug of the theme or the parent theme if a child theme is active. For the theme <em>Schlicht<\/em>, the value to check for is <code class=\"lang-php\">schlicht<\/code>.<\/p>\n\n\n\n<p>That is the function:<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/**\n * Remove upgrade URL option after switching the theme,\n * if the new theme is not schlicht or a child theme of schlicht\n *\/<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">schlicht_remove_upgrade_url<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    $theme_object = wp_get_theme();\n    $template     = $theme_object-&gt;template;\n    <span class=\"hljs-keyword\">if<\/span> ( $template == <span class=\"hljs-string\">'schlicht'<\/span> ) {\n\n    } <span class=\"hljs-keyword\">else<\/span> {\n        delete_option( <span class=\"hljs-string\">'schlicht_upgrade_url'<\/span> );\n    }\n}\n\nadd_action( <span class=\"hljs-string\">'switch_theme'<\/span>, <span class=\"hljs-string\">'schlicht_remove_upgrade_url'<\/span>, <span class=\"hljs-number\">10<\/span>, <span class=\"hljs-number\">2<\/span> );<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If <code class=\"lang-php\">$theme_object-&gt;template<\/code> has the value <code class=\"lang-php\">schlicht<\/code>, either the theme <em>Schlicht<\/em> or one of its child themes is active. If that is not the case, we remove the customizer option\u2019s value with <code class=\"lang-php\">delete_option( 'schlicht_upgrade_url' );<\/code>.<\/p>\n\n\n\n<p>That is it. We created an automatic update system for themes which are not located in the W.org directory.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can install automatic updates for themes from the WordPress.org directory. Here I show you how you can provide these automatic updates for themes, which are not in the directory.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wpf_show_in_dewp_planet_feed":false,"flobn_post_versions":"","webmentions_disabled_pings":false,"webmentions_disabled":false,"lazy_load_responsive_images_disabled":false,"footnotes":""},"categories":[115],"tags":[],"class_list":["post-3388","post","type-post","status-publish","format-standard","hentry","category-wordpress-snippets"],"wp-worthy-pixel":{"ignored":false,"public":"c9886c349ba34e6f93dc24dd68e08ac2","server":"vg01.met.vgwort.de","url":"https:\/\/vg01.met.vgwort.de\/na\/c9886c349ba34e6f93dc24dd68e08ac2"},"wp-worthy-type":"normal","_links":{"self":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/3388","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/comments?post=3388"}],"version-history":[{"count":8,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/3388\/revisions"}],"predecessor-version":[{"id":5919,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/3388\/revisions\/5919"}],"wp:attachment":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/media?parent=3388"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/categories?post=3388"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/tags?post=3388"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}