<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>jetpack &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/jetpack/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Thu, 24 Oct 2024 07:28:25 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>jetpack &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Liberate your daily statistics from JetPack]]></title>
		<link>https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/</link>
					<comments>https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 17 Oct 2024 11:34:16 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[jetpack]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=53473</guid>

					<description><![CDATA[Because Ma.tt continues to burn all of the goodwill built up by WordPress, and JetPack have decided to charge a ridiculous sum for their statistics, I&#039;ve decided to move to a new stats provider.  But I don&#039;t want to lose all the statistics I&#039;ve built up over the years.  How do I download a day-by-day export of my JetPack stats?  Luckily, there is an API for downloading all your JetPack stats! …]]></description>
										<content:encoded><![CDATA[<p>Because Ma.tt continues to burn all of the goodwill built up by WordPress, and JetPack have decided to charge a ridiculous sum for their statistics, I've decided to move to a new stats provider.  But I don't want to lose all the statistics I've built up over the years.</p>

<p>How do I download a day-by-day export of my JetPack stats<sup id="fnref:dl"><a href="https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/#fn:dl" class="footnote-ref" title="When people ask on the official support forum, they're told to privately contact JetPack. There's a help page which shows how to download a summary. But I couldn't find anything more fine-grained…" role="doc-noteref">0</a></sup>?</p>

<p>Luckily, there is <a href="https://stats.wordpress.com/csv.php">an API for downloading all your JetPack stats</a>!</p>

<p>First, get your API key by visiting <a href="https://apikey.wordpress.com/">https://apikey.wordpress.com/</a> - it should be a 12 character string. For this example, I'm going to use 123456789012. You will need to use your own API key.</p>

<p>There is some brief documentation on that page. Here are the bits we are interested in:</p>

<pre><code class="language-_">api_key     String    A secret unique to your WordPress.com user account.
blog_uri    String    The full URL to the root directory of your blog. Including the full path.
table       String    One of views, postviews, referrers, referrers_grouped, searchterms, clicks, videoplays.
end         String    The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01) and default is UTC date.
days        Integer   The length of the desired time frame. Default is 30. "-1" means unlimited.
limit       Integer   The maximum number of records to return. Default is 100. "-1" means unlimited. If days is -1, limit is capped at 500.
format      String    The format the data is returned in, 'csv', 'xml' or 'json'. Default is 'csv'.
</code></pre>

<p>In order to get all of the statistics from a single day, the URl is:</p>

<pre><code class="language-_">https://stats.wordpress.com/csv.php?api_key=123456789012
     &amp;blog_uri=https://shkspr.mobi/blog/
     &amp;table=postviews
     &amp;end=2024-10-07
     &amp;days=1
     &amp;limit=-1
</code></pre>

<p>That gets all of the statistics from <em>one</em> specific day. The <code>limit=-1</code> means it will retrieve all the records of that day<sup id="fnref:day"><a href="https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/#fn:day" class="footnote-ref" title="The maximum number of records for a specific day in my dataset was 978." role="doc-noteref">1</a></sup>.</p>

<p>That will get you a CSV which looks like:</p>

<pre><code class="language-csv">"date","post_id","post_title","post_permalink","views"
"2024-10-09",0,"Home page","https://shkspr.mobi/blog/",59
"2024-10-09",42171,"Review: HP's smallest laser printer - M140w + Linux set up","https://shkspr.mobi/blog/2022/04/review-hps-smallest-laser-printer-m140w-linux-set-up/",7
"2024-10-09",49269,"No, Oscar Wilde did not say ""Imitation is the sincerest form of flattery that mediocrity can pay to greatness""","https://shkspr.mobi/blog/2024/01/no-oscar-wilde-did-not-say-imitation-is-the-sincerest-form-of-flattery-that-mediocrity-can-pay-to-greatness/",7
"2024-10-09",53333,"The Cleaner 🆚 Der Tatortreiniger - Series 3","https://shkspr.mobi/blog/2024/10/the-cleaner-%f0%9f%86%9a-der-tatortreiniger-series-3/",7
"2024-10-09",49943,"Solved! ""Access Point Name settings are not available for this user""","https://shkspr.mobi/blog/2024/03/solved-access-point-name-settings-are-not-available-for-this-user/",7
"2024-10-09",43690,"WhatsApp Web for Android - a reasonable compromise?","https://shkspr.mobi/blog/2022/11/whatsapp-web-for-android-a-reasonable-compromise/",5
</code></pre>

<p>You can also get a JSON file using <code>&amp;format=json</code>, although it doesn't contain the permalinks.</p>

<pre><code class="language-json">[
    {
        "date": "2024-10-09",
        "postviews": [
            {
                "post_id": 0,
                "post_title": "",
                "permalink": "",
                "views": 59
            },
            {
                "post_id": 49269,
                "post_title": "No, Oscar Wilde did not say \"Imitation is the sincerest form of flattery that mediocrity can pay to greatness\"",
                "permalink": "",
                "views": 9
            },
            {
                "post_id": 42171,
                "post_title": "Review: HP's smallest laser printer - M140w + Linux set up",
                "permalink": "",
                "views": 7
            },
</code></pre>

<p>From there, I wrote a scrap of Python to download every single date individually.</p>

<pre><code class="language-python">import requests
import datetime
import os
import json

# Directory to save the JSON files
save_dir = "jetpack_stats"
os.makedirs(save_dir, exist_ok=True)

# URL of the API
base_url = "https://stats.wordpress.com/csv.php?api_key=123456789012"+\
           "&amp;blog_uri=https://example.com/"+\
           "&amp;table=postviews"+\
           "&amp;days=1"+\
           "&amp;format=json"+\
           "&amp;limit=-1"+\
           "&amp;end="

# Make API call and save the response
def fetch_and_save_json(date):
    # Format the date as ISO8601 (YYYY-MM-DD)
    formatted_date = date.isoformat()

    # Make the API call
    url = f"{base_url}{formatted_date}"
    response = requests.get(url)

    if response.status_code == 200:
        data = response.json()
        file_name = f"{formatted_date}.json"
        file_path = os.path.join(save_dir, file_name)
        with open(file_path, "w") as f:
            json.dump(data, f, indent=4)

        print(f"Saved {formatted_date}")
    else:
        print(f"Failed! {formatted_date} status code: {response.status_code}")

# Iterate over a date range
start_date = datetime.date(2023,  1 , 1)
end_date   = datetime.date(2024, 10, 30)

# Loop through all dates 
current_date = start_date
while current_date &lt;= end_date:
    fetch_and_save_json(current_date)
    current_date += datetime.timedelta(days=1)
</code></pre>

<p>You'll need to manually find the earliest date for which your blog has statistics.</p>

<p>Running the code is a little slow. Expect about 3 minutes per year of data. I'm sure you could parallelise it if you really needed to.</p>

<p>Now, for my next trick, how do I import these data into a <em>new</em> stats plugin? That's tomorrow's blog post!</p>

<div id="footnotes" role="doc-endnotes">
<hr>
<ol start="0">

<li id="fn:dl">
<p>When people ask on the official support forum, they're <a href="https://wordpress.org/support/topic/how-do-we-export-all-stats-data-held-on-jetpack-servers/">told to privately contact JetPack</a>. There's a help page which shows <a href="https://wordpress.com/support/stats/#download-stats">how to download a summary</a>. But I couldn't find anything more fine-grained than that.&nbsp;<a href="https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/#fnref:dl" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:day">
<p>The maximum number of records for a specific day in my dataset was 978.&nbsp;<a href="https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/#fnref:day" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=53473&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/10/liberate-your-daily-statistics-from-jetpack/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Liberate your Markdown posts from JetPack in WordPress]]></title>
		<link>https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/</link>
					<comments>https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 25 Aug 2024 11:34:20 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[jetpack]]></category>
		<category><![CDATA[markdown]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=51755</guid>

					<description><![CDATA[A scrap of code which I hope helps you.  Problem  You installed the WordPress JetPack plugin and wrote all your blog posts in Markdown. Now you want to remove JetPack or replace it with a better Markdown parser.  You turn off JetPack&#039;s &#34;Write posts or pages in plain-text Markdown syntax&#34;.  You click edit on a post and see the HTML version of your page. Where did the Markdown version go? …]]></description>
										<content:encoded><![CDATA[<p>A scrap of code which I hope helps you.</p>

<h2 id="problem"><a href="https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/#problem">Problem</a></h2>

<p>You installed the WordPress JetPack plugin and wrote all your blog posts in Markdown. Now you want to remove JetPack or replace it with a better Markdown parser.</p>

<p>You turn off JetPack's "Write posts or pages in plain-text Markdown syntax".  You click edit on a post and see the HTML version of your page. Where did the Markdown version go?</p>

<h2 id="background"><a href="https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/#background">Background</a></h2>

<p>When you write using JetPack's Markdown plugin, the Markdown version is stored in <code>post_content_filtered</code>. When you hit "publish" or "update", the page is parsed as Markdown and the HTML output is stored in <code>post_content</code>.</p>

<p>When you hit "edit", the <code>post_content_filtered</code> version is loaded into the editor - and the process starts again.</p>

<h2 id="solution"><a href="https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/#solution">Solution</a></h2>

<p>When you edit a post, replace the content with the filtered version, then delete the filtered version.</p>

<p>Place this code in your theme's <code>functions.php</code>.</p>

<pre><code class="language-php">function edit_markdown_content( $content, $id ) {
    $post = get_post( $id );
    if ( $post &amp;&amp; ! empty( $post-&gt;post_content_filtered ) ) {
        //  Get the Markdown version
        $markdown = $post-&gt;post_content_filtered;

        //  Delete the post_content_filtered version
        global $wpdb;
        $debug = $wpdb-&gt;query( 
            $wpdb-&gt;prepare(
                "UPDATE $wpdb-&gt;posts SET `post_content_filtered` = '' WHERE `wp_posts`.`ID` = %d",
                                                                                              $id
            )
        );

        //  Replace the post_content with the Markdown version
        $post-&gt;post_content = $markdown;

        //  Send it to the editor with a message saying that it was restored, along with the date of restoration
        return "&lt;!-- Restored from post_content_filtered \n" . date("c") . "\n--&gt;" . $post-&gt;post_content;
    }
    return $post-&gt;post_content;
}
add_filter( "edit_post_content", "edit_markdown_content", 1, 2 );
</code></pre>

<p>I adapted it from <a href="https://github.com/terrylinooo/githuber-md/blob/5ae517a549600f719645d35baf30b29a8069ebcc/src/Controllers/Markdown.php#L1111">WP Githuber MD</a></p>

<h2 id="direct-mysql"><a href="https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/#direct-mysql">Direct MySQL</a></h2>

<p>If you want to automatically convert all your posts, you can edit your database directly.</p>

<pre><code class="language-mysql">UPDATE wp_posts
SET 
    post_content = post_content_filtered,
    post_content_filtered = ''
WHERE post_content_filtered IS NOT NULL AND post_content_filtered&lt;&gt;'';
</code></pre>

<h2 id="warning"><a href="https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/#warning">Warning</a></h2>

<p>If you do not have a Markdown parser installed, posts will come out looking *very* strange.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=51755&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/08/liberate-your-markdown-posts-from-jetpack-in-wordpress/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Rewriting WordPress's JetPack Related Posts Shortcode]]></title>
		<link>https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/</link>
					<comments>https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 09 Oct 2023 11:34:34 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[jetpack]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=47385</guid>

					<description><![CDATA[I like the JetPack related post functionality. But I wanted to customise it far beyond what the default code allows for.  So here&#039;s how I went from this:    To this:    Documentation  The complete documentation for related posts is pretty easy to follow.  This is an adaptation of &#34;Use Jetpack_RelatedPosts_Raw to build your own list of Related Posts&#34;.  Remove the automatic placement  You can turn…]]></description>
										<content:encoded><![CDATA[<p>I like the JetPack related post functionality. But I wanted to customise it far beyond what the default code allows for.</p>

<p>So here's how I went from this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2023/10/old-layout-fs8.png" alt="The old layout has three items, with small images and indistinct text." width="540" height="610" class="aligncenter size-full wp-image-47387">

<p>To this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2023/10/suggested-fs8.png" alt="The new layout has 4 items, each boxed off, with a larger image and more distinct text." width="540" class="aligncenter size-full wp-image-48365">

<h2 id="documentation"><a href="https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#documentation">Documentation</a></h2>

<p>The <a href="https://jetpack.com/support/related-posts/customize-related-posts/">complete documentation for related posts</a> is pretty easy to follow.</p>

<p>This is an adaptation of "<a href="https://jetpack.com/support/related-posts/customize-related-posts/#raw">Use <code>Jetpack_RelatedPosts_Raw</code> to build your own list of Related Posts</a>".</p>

<h3 id="remove-the-automatic-placement"><a href="https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#remove-the-automatic-placement">Remove the automatic placement</a></h3>

<p>You can turn off the original "related posts" by adding this to your theme's <code>functions.php</code>:</p>

<pre><code class="language-php">function jetpackme_remove_rp() {
    if ( class_exists( 'Jetpack_RelatedPosts' ) ) {
        $jprp = Jetpack_RelatedPosts::init();
        $callback = array( $jprp, 'filter_add_target_to_dom' );
        remove_filter( 'the_content', $callback, 40 );
    }
}
add_filter( 'wp', 'jetpackme_remove_rp', 20 );
</code></pre>

<h3 id="add-the-new-related-posts"><a href="https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#add-the-new-related-posts">Add the new Related Posts</a></h3>

<p>In your theme's <code>index.php</code> (or wherever else you like) you can add this code to insert the new related posts functionality:</p>

<pre><code class="language-php">if ( is_single() ) {
    echo "&lt;section&gt;";
        echo do_shortcode( '[jprelp]' );
    echo "&lt;/section&gt;";
}
</code></pre>

<h3 id="create-the-new-functionality"><a href="https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#create-the-new-functionality">Create the new functionality</a></h3>

<p>And this goes in your theme's <code>functions.php</code> file. I've commented it as best I can. Let me know if you need more info.</p>

<pre><code class="language-php">function jetpackme_custom_related() {
    //  Check that JetPack Related Posts exists
    if (
            class_exists( 'Jetpack_RelatedPosts' )
            &amp;&amp; method_exists( 'Jetpack_RelatedPosts', 'init_raw' )
    ) {
            //  Get the related posts
            $related = Jetpack_RelatedPosts::init_raw()
                -&gt;set_query_name( 'edent-related-shortcode' ) 
                -&gt;get_for_post_id(
                    get_the_ID(),   //  ID of the post
                    array( 'size' =&gt; 4 )//  How many related items to fetch
                );
            if ( $related ) {
                //  Set the container for the related posts
                $output = "&lt;h2 id='related-posts'&gt;The Algorithm™ suggests:&lt;/h2&gt;";
                $output .=   "&lt;ul class='related-posts'&gt;";

                foreach ( $related as $result ) {
                    $related_post_id = $result['id'];

                    // Get the related post
                    $related_post = get_post( $related_post_id );

                    //  Get the attributes
                    $related_post_title = $related_post-&gt;post_title;
                    $related_post_date  = substr( $related_post-&gt;post_date, 0, 4 ); // YYYY-MM-DD
                    $related_post_link  = get_permalink( $related_post_id );

                    //  Get the thumbnail
                    if ( has_post_thumbnail( $related_post_id) ) {
                        $related_post_thumb = get_the_post_thumbnail( $related_post_id, 'full', 
                            array( "class"   =&gt; "related-post-img",
                                   "loading" =&gt; "lazy" //   Lazy loading and other attributes
                            ) 
                        );
                    } else {
                        $related_post_thumb = null;
                    }

                    //  Create the HTML for the related post
                    $output .= '&lt;li class="related-post"&gt;';
                    $output .=    "&lt;a href='{$related_post_link}'&gt;";
                    $output .=       "{$related_post_thumb}&lt;p&gt;{$related_post_title}&lt;/p&gt;&lt;/a&gt;";
                    $output .=    "&lt;time&gt;{$related_post_date}&lt;/time&gt;";
                    $output .= "&lt;/li&gt;";
                }
                //  Finish the related posts container
                $output .="&lt;/ul&gt;";
            }
        //  Display the related posts
        echo $output;
    }
}
add_shortcode( 'jprel', 'jetpackme_custom_related' );   //  Shortcode name can be whatever you want
</code></pre>

<h3 id="bit-of-css-to-zhuzh-it-up"><a href="https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#bit-of-css-to-zhuzh-it-up">Bit of CSS to zhuzh it up</a></h3>

<p>Feel free to add your own styles. This is what works for me.</p>

<pre><code class="language-css">.related-posts  {
    list-style: none;
    padding: 0;
    display: inline-flex;
    width: 100%;
    flex-wrap: wrap;
    justify-content: center;
}

.related-posts &gt; * {
    /* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Controlling_ratios_of_flex_items_along_the_main_axis#combining_flex-grow_and_flex-basis */
    flex: 1 1 0;
}

    .related-post {
        min-width: 10em;
        max-width: 20em;
        text-align: center;
        margin: .25em;
        border: .1em var(--color-text);
        border-style: solid;
        border-radius: var(--border-radius);
        position: relative;
        display: flex;
        flex-direction: column;
        min-height: 100%;
        overflow: clip;
    }

        .related-post h3 {
            font-size: 1em;
            padding-top: .5em;
        }

        .related-post img {
            object-fit: cover;
            height: 9em;
            width: 100%;
            border-radius: 0 1em 0 0;
            background: var(--color-text);
            display: inline-block;
        }
        .related-post p {
            margin: 0 .25em;
        }
        .related-post time {
            font-size: .75em;
            display: block;
        }
</code></pre>

<h2 id="todo"><a href="https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/#todo">ToDo</a></h2>

<ul>
<li>Use transients to store the data to prevent repeated slow API calls?</li>
<li>Perhaps some teaser text?</li>
<li>Adjust the layout so the date always floats to the bottom?</li>
</ul>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=47385&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/10/rewriting-wordpresss-jetpack-related-posts-shortcode/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Getting WordPress / JetPack Subscriber Counts via the API... the hard way]]></title>
		<link>https://shkspr.mobi/blog/2023/09/getting-wordpress-jetpack-subscriber-counts-via-the-api/</link>
					<comments>https://shkspr.mobi/blog/2023/09/getting-wordpress-jetpack-subscriber-counts-via-the-api/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 14 Sep 2023 11:34:13 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[jetpack]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46775</guid>

					<description><![CDATA[People can subscribe to receive my blog via email. This is managed by the JetPack plugin.  I want to be able to display something like &#34;Join 1,234 subscribers and receive updates via email&#34;. So, how do I get the subscriber count from the API?  As documented in the JetPack HTTP API, it is possible to interact with JetPack programmatically.  A good starting point is /wp-json/ - that will show you…]]></description>
										<content:encoded><![CDATA[<p>People can subscribe to receive my blog via email. This is managed by the JetPack plugin.</p>

<p>I want to be able to display something like "Join 1,234 subscribers and receive updates via email". So, how do I get the subscriber count from the API?</p>

<p>As documented in the <a href="https://github.com/Automattic/jetpack/blob/trunk/docs/rest-api.md">JetPack HTTP API</a>, it is possible to interact with JetPack programmatically.</p>

<p>A good starting point is <a href="https://shkspr.mobi/blog/wp-json/"><code>/wp-json/</code></a> - that will show you all the API endpoints available on your blog.</p>

<p>By filtering on "subscribers", we find:</p>

<pre><code class="language-json">"/jetpack/v4/stats-app/sites/12345/subscribers/counts": {
    "namespace": "jetpack/v4/stats-app",
    "methods": ["GET"],
    "endpoints": [{
        "methods": ["GET"],
        "args": []
    }],
    "_links": {
        "self": [{
            "href": "https://shkspr.mobi/blog/wp-json/jetpack/v4/stats-app/sites/12345/subscribers/counts"
        }]
    }
},
</code></pre>

<p>Can we just visit that URl and get the data? Nope!</p>

<p>If you're logged in to your blog, and on the JetPack dashboard, you can run this scrap of Javascript in your browser's console:</p>

<pre><code class="language-js">fetch( '/wp-json/jetpack/v4/stats-app/sites/12345/subscribers/counts', {
    credentials: 'same-origin',
    headers: {
        'X-WP-Nonce': Initial_State.WP_API_nonce,
        'Content-type': 'application/json' }
} )
    .then( response =&gt; response.json() )
    .then( response =&gt; console.log( response) )
    .catch( error =&gt; console.log( error.responseText ) );
</code></pre>

<p>That returns:</p>

<pre><code class="language-json">{
  "counts": {
    "email_subscribers": 443,
    "social_followers": 0,
    "paid_subscribers": 0
  }
}
</code></pre>

<p>OK! So we know it is possible for an admin to get these data. How can we regularly fetch the count from the API and cache it for later use?</p>

<p>Well, that's where I get stuck.  The <a href="https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/">documentation for the WordPress REST API</a> says this:</p>

<blockquote><p>It is important to keep in mind that this authentication method relies on WordPress cookies. As a result this method is only applicable when the REST API is used inside of WordPress and the current user is logged in. In addition, the current user must have the appropriate capability to perform the action being performed.</p></blockquote>

<p>So we're stuck without a cookie? Not quite!</p>

<p>The <a href="https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/#basic-authentication-with-application-passwords">documentation also says we can use Application Passwords</a>. You can <a href="https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/">read more about them</a> but, basically, go to your user profile screen at <code>/wp-admin/profile.php</code> and you should be able to generate an app password.</p>

<p>Once done, you can test it by running this on the command line:</p>

<pre><code class="language-bash">curl --user "admin:p4ssw0rd" https://example.com/wp-json/jetpack/v4/stats-app/sites/1234/subscribers/counts
</code></pre>

<p>(Obviously, use your own username, password, URl, and site ID.)</p>

<p>This means we can add something like this to our <code>functions.php</code> file to get the data and store it for a day:</p>

<pre><code class="language-php">&lt;?php
if( get_transient( 'jp_sub_stats' ) ) {
    $jp_sub_stats = get_transient( 'jp_sub_stats' );
} else {
    $headers = array(
        'Authorization' =&gt; 'Basic ' . base64_encode( 'admin:p4ssw0rd' )
    );

    $api_url = 'https://example.com/wp-json/jetpack/v4/stats-app/sites/1234/subscribers/counts';

    $response = wp_remote_request(
        $api_url,
        array( 'headers'   =&gt; $headers )
    );

    $json = json_decode( $response );
    $count = $json-&gt;counts-&gt;email_subscribers;

    set_transient( 'jp_sub_stats', $count, DAY_IN_SECONDS );
}
</code></pre>

<p>Of course, you'll probably want to store the password somewhere securely rather than in your source code. And you'll probably want to do some error checking on what the API returns.</p>

<p>But, there you go. A somewhat convoluted way to get your JetPack subscriber count via an API call.  Enjoy!</p>

<h2 id="the-easy-way"><a href="https://shkspr.mobi/blog/2023/09/getting-wordpress-jetpack-subscriber-counts-via-the-api/#the-easy-way">The Easy Way</a></h2>

<p>Oh! You wanted to do this with the minimum of fuss?</p>

<p><a href="https://github.com/Automattic/jetpack/blob/d65d8529885a3e709d2554570ded68bb0b8f3007/projects/plugins/jetpack/extensions/blocks/subscriptions/subscriptions.php#L183">There's a built in function</a> which stores the count in a transient.</p>

<p>So you can simply do:</p>

<pre><code class="language-php">$cache_key  = 'wpcom_subscribers_total';
$subs       = get_transient( $cache_key );
$subs_count = $subs["value"];
</code></pre>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46775&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/09/getting-wordpress-jetpack-subscriber-counts-via-the-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Better Footnotes in WordPress JetPack]]></title>
		<link>https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/</link>
					<comments>https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 24 Oct 2021 11:34:47 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[footnotes]]></category>
		<category><![CDATA[jetpack]]></category>
		<category><![CDATA[markdown]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=40717</guid>

					<description><![CDATA[Previously, I&#039;ve written about using Footnotes in WordPress Markdown.  A reader notified me that the footnotes weren&#039;t very accessible. This blog post describes the problem and proposes a solution.  The Problem  Using WordPress&#039;s JetPack, markdown footnotes are rendered as:  Some text &#60;sup id=&#34;fnref-1234-1&#34;&#62;&#60;a href=&#34;#fn-1234-1&#34; class=&#34;jetpack-footnote&#34;&#62;1&#60;/a&#62;&#60;/sup&#62; ... &#60;li id=&#34;fn-1234-1&#34;&#62;The…]]></description>
										<content:encoded><![CDATA[<p>Previously, I've written about using <a href="https://shkspr.mobi/blog/2021/08/footnotes-in-markdown/">Footnotes in WordPress Markdown</a>.  A reader notified me that the footnotes<sup id="fnref:fn"><a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#fn:fn" class="footnote-ref" title="Like this!" role="doc-noteref">0</a></sup> weren't very accessible. This blog post describes the problem and proposes a solution.</p>

<h2 id="the-problem"><a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#the-problem">The Problem</a></h2>

<p>Using WordPress's JetPack, markdown footnotes are rendered as:</p>

<pre><code class="language-html">Some text &lt;sup id="fnref-1234-1"&gt;&lt;a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#fn-1234-1" class="jetpack-footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;
...
&lt;li id="fn-1234-1"&gt;The footnotes.&nbsp;&lt;a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#fnref-1234-1"&gt;↩&lt;/a&gt;&lt;/li&gt;
</code></pre>

<p>There are two main problems with this:</p>

<ol>
<li>The <code>&lt;sup&gt;1&lt;/sup&gt;</code> doesn't announce the destination of the link.</li>
<li>The ↩ may be read out as "leftwards arrow with hook"</li>
</ol>

<p>Ideally, it would be useful to have something like:</p>

<pre><code class="language-html">&lt;a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#fn-1234-1" aria-label="Read footnote 1" ...
</code></pre>

<p>and</p>

<pre><code class="language-html">&lt;a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#fnref-1234-1" aria-label="Return to main content"&gt;↩&lt;/a&gt;
</code></pre>

<p>Or similar.</p>

<h2 id="the-solution"><a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#the-solution">The Solution</a></h2>

<p>The issue occurs in <code>jetpack/_inc/lib/markdown/extra.php</code></p>

<pre><code class="language-php">@define( 'MARKDOWN_FN_LINK_TITLE',     "" );
@define( 'MARKDOWN_FN_BACKLINK_TITLE', "" );
</code></pre>

<p>The link and backlink titles are used later in the same file to set the title.</p>

<pre><code class="language-php">if ($this-&gt;fn_backlink_title != "") {
    $title = $this-&gt;fn_backlink_title;
    $title = $this-&gt;encodeAttribute($title);
    $attr .= " title=\"$title\"";
}
</code></pre>

<p>But, because they're never set, the code doesn't run.</p>

<p>It's pretty easy to hotfix this. Replace the above definitions with:</p>

<pre><code class="language-php">@define( 'MARKDOWN_FN_LINK_TITLE',     __( 'Read footnote.', 'jetpack' ) );
@define( 'MARKDOWN_FN_BACKLINK_TITLE', __( 'Return to main content.', 'jetpack' ) );
</code></pre>

<p>I've <a href="https://github.com/Automattic/jetpack/issues/21371">raised this as an issue on GitHub</a>.</p>

<h2 id="problems-with-the-solution"><a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#problems-with-the-solution">Problems with the solution.</a></h2>

<ul>
<li>It will be overwritten when JetPack updates. You can also add the new <code>@define</code>s to your theme if you want them to survive updates.</li>
<li>I'm not sure if <code>title</code> is the right element. Although it seems has the same precedence as <code>aria-label</code> in the <a href="https://www.w3.org/TR/accname-1.1/#mapping_additional_nd_te">Accessible Name and Description Computation algorithm</a></li>
<li>It is in English only. Because those strings don't exit in the translation file, it will only work for monolingual installs.</li>
<li>The title text shows as a pop-up on certain browsers.</li>
<li>No metadata. It might be nice to have the <code>title</code> say "Read footnote <strong>23</strong>."</li>
<li>Some footnote plugins put the footnote text in the <code>title</code>. That may be useful.</li>
</ul>

<p>If you'd like to see this in the default JetPack experience - please <a href="https://github.com/Automattic/jetpack/issues/21371">leave a comment on GitHub</a>.</p>

<div id="footnotes" role="doc-endnotes">
<hr>
<ol start="0">

<li id="fn:fn">
<p>Like this!&nbsp;<a href="https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/#fnref:fn" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=40717&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/10/better-footnotes-in-wordpress-jetpack/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
