> All in One 586: Creating a “Starred” Feed

Ads

Tuesday, January 21, 2025

Creating a “Starred” Feed

Chris wrote about “Likes” pages a long while back. The idea is rather simple: “Like” an item in your RSS reader and display it in a feed of other liked items. The little example Chris made is still really good.

There were two things Chris noted at the time. One was that he used a public CORS proxy that he wouldn’t use in a production environment. Good idea to nix that, security and all. The other was that he’d consider using WordPress transients to fetch and cache the data to work around CORS.

I decided to do that! The result is this WordPress block I can drop right in here. I’ll plop it in a <details> to keep things brief.

Open Starred Feed
Link on 1/15/2025

Learning HTML is the best investment I ever did

One of the running jokes and/or discussion I am sick and tired of is people belittling HTML. Yes, HTML is not a programming language. No, HTML should not just be a compilation target. Learning HTML is a solid investment and not hard to do.

I am not…

Link on 1/6/2025

The :empty pseudo-class

We can use the :empty pseudo-class as a way to style elements on your webpage that are empty.

You might wonder why you’d want to style something that’s empty. Let’s say you’re creating a todo list.

You want to put your todo items in a list, but what about when you don’t…

Link on 1/8/2025

CSS Wish List 2025

Back in 2023, I belatedly jumped on the bandwagon of people posting their CSS wish lists for the coming year.  This year I’m doing all that again, less belatedly! (I didn’t do it last year because I couldn’t even.  Get it?)

I started this post by looking at what I…

Link on 1/9/2025

aria-description Does Not Translate

It does, actually. In Firefox. Sometimes.

A major risk of using ARIA to define text content is it typically gets overlooked in translation. Automated translation services often do not capture it. Those who pay for localization services frequently miss content in ARIA attributes when sending text strings to localization vendors.

Content buried…

It’s a little different. For one, I’m only fetching 10 items at a time. We could push that to infinity but that comes with a performance tax, not to mention I have no way of organizing the items for them to be grouped and filtered. Maybe that’ll be a future enhancement!

The Chris demo provided the bones and it does most of the heavy lifting. The “tough” parts were square-pegging the thing into a WordPress block architecture and then getting transients going. This is my first time working with transients, so I thought I’d share the relevant code and pick it apart.

function fetch_and_store_data() {
  $transient_key = 'fetched_data';
  $cached_data = get_transient($transient_key);

  if ($cached_data) {
    return new WP_REST_Response($cached_data, 200);
  }

  $response = wp_remote_get('https://feedbin.com/starred/a22c4101980b055d688e90512b083e8d.xml');
  if (is_wp_error($response)) {
    return new WP_REST_Response('Error fetching data', 500);
  }

  $body = wp_remote_retrieve_body($response);
  $data = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NOCDATA);
  $json_data = json_encode($data);
  $array_data = json_decode($json_data, true);

  $items = [];
  foreach ($array_data['channel']['item'] as $item) {
    $items[] = [
      'title' => $item['title'],
      'link' => $item['link'],
      'pubDate' => $item['pubDate'],
      'description' => $item['description'],
    ];
  }

  set_transient($transient_key, $items, 12 * HOUR_IN_SECONDS);

  return new WP_REST_Response($items, 200);
}

add_action('rest_api_init', function () {
  register_rest_route('custom/v1', '/fetch-data', [
    'methods' => 'GET',
    'callback' => 'fetch_and_store_data',
  ]);
});

Could this be refactored and written more efficiently? All signs point to yes. But here’s how I grokked it:

function fetch_and_store_data() {

}

The function’s name can be anything. Naming is hard. The first two variables:

$transient_key = 'fetched_data';
$cached_data = get_transient($transient_key);

The $transient_key is simply a name that identifies the transient when we set it and get it. In fact, the $cached_data is the getter so that part’s done. Check!

I only want the $cached_data if it exists, so there’s a check for that:

if ($cached_data) {
  return new WP_REST_Response($cached_data, 200);
}

This also establishes a new response from the WordPress REST API, which is where the data is cached. Rather than pull the data directly from Feedbin, I’m pulling it and caching it in the REST API. This way, CORS is no longer an issue being that the starred items are now locally stored on my own domain. That’s where the wp_remote_get() function comes in to form that response from Feedbin as the origin:

$response = wp_remote_get('https://feedbin.com/starred/a22c4101980b055d688e90512b083e8d.xml');

Similarly, I decided to throw an error if there’s no $response. That means there’s no freshly $cached_data and that’s something I want to know right away.

if (is_wp_error($response)) {
  return new WP_REST_Response('Error fetching data', 500);
}

The bulk of the work is merely parsing the XML data I get back from Feedbin to JSON. This scours the XML and loops through each item to get its title, link, publish date, and description:

$body = wp_remote_retrieve_body($response);
$data = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NOCDATA);
$json_data = json_encode($data);
$array_data = json_decode($json_data, true);

$items = [];
foreach ($array_data['channel']['item'] as $item) {
  $items[] = [
    'title' => $item['title'],
    'link' => $item['link'],
    'pubDate' => $item['pubDate'],
    'description' => $item['description'],
  ];
}

“Description” is a loaded term. It could be the full body of a post or an excerpt — we don’t know until we get it! So, I’m splicing and trimming it in the block’s Edit component to stub it at no more than 50 words. There’s a little risk there because I’m rendering the HTML I get back from the API. Security, yes. But there’s also the chance I render an open tag without its closing counterpart, muffing up my layout. I know there are libraries to address that but I’m keeping things simple for now.

Now it’s time to set the transient once things have been fetched and parsed:

set_transient($transient_key, $items, 12 * HOUR_IN_SECONDS);

The WordPress docs are great at explaining the set_transient() function. It takes three arguments, the first being the $transient_key that was named earlier to identify which transient is getting set. The other two:

  • $value: This is the object we’re storing in the named transient. That’s the $items object handling all the parsing.
  • $expiration: How long should this transient last? It wouldn’t be transient if it lingered around forever, so we set an amount of time expressed in seconds. Mine lingers for 12 hours before it expires and then updates the next time a visitor hits the page.

OK, time to return the items from the REST API as a new response:

return new WP_REST_Response($items, 200);

That’s it! Well, at least for setting and getting the transient. The next thing I realized I needed was a custom REST API endpoint to call the data. I really had to lean on the WordPress docs to get this going:

add_action('rest_api_init', function () {
  register_rest_route('custom/v1', '/fetch-data', [
    'methods' => 'GET',
    'callback' => 'fetch_and_store_data',
  ]);
});

That’s where I struggled most and felt like this all took wayyyyy too much time. Well, that and sparring with the block itself. I find it super hard to get the front and back end components to sync up and, honestly, a lot of that code looks super redundant if you were to scope it out. That’s another story altogether.

Enjoy reading what we’re reading! I put a page together that pulls in the 10 most recent items with a link to subscribe to the full feed.


Creating a “Starred” Feed originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/1jN4paf
via IFTTT

No comments:

Post a Comment

Clear today!

With a high of F and a low of 17F. Currently, it's 25F and Clear outside. Current wind speeds: 10 from the West Pollen: 0 Sunris...