In the post Pondering a Jupyter Notebooks to WordPress Publishing Pattern: MultiMarker Map Widget, I described a simple pattern I started exploring last year that used a custom WordPress shortcode plugin to render data added to one or more custom fields associated with a WordPress post; the post text (including shortcode) and custom fields data were themselves posted into WordPress using some Python code executed from a Jupyter notebook. The idea behind that pattern was to provide a way of automating the creation of custom posts largely from a supplied data set, rendered using a generic shortcode plugin.
Another pattern I explored last year used the WordPress Transients API to cache data pulled from a 3rd party API in the WordPress database, and allow that data to be used by a custom plugin to render the post.
Here’s some example code for a plugin that renders a map containing recent planning applications on the Isle of Wight: the data is grabbed via an API from a morph.io webscraper, which scrapes the data from the Isle of Wight council website.
The two key bits of the script are where I check to see if cached data exisits ( get_transient( 'iwcurrplanningitems' ); and if it doesn’t, grab a recent copy from the API and cache it for 8 hours (set_transient('iwcurrplanningitems', $markers, 60*60*8);).
<?php /* Plugin Name: IWPlanningLeafletMap Description: Shortcode to render an interactive map displaying clustered markers. Markers are pulled in via JSON from an external URL. Intended primarily to supported automated post creation. Inspired by folium python library and Google Maps v3 Shortcode multiple Markers WordPress plugin Version: 1.0 Author: Tony Hirst */ //Loaded in from multimarker shortcode add_action( 'wp_enqueue_scripts', 'custom_scripts' ); add_action( 'wp_enqueue_scripts', 'custom_styles' ); // Add stuff to header add_action('wp_head', 'IWPlanningLeafletMap_header'); add_action('wp_head', 'fix_css'); /* function fix_css() { echo '<style type="text/css">#map { position:absolute; top:0; bottom:0; right:0; left:0; }</style>' . "\n"; } */ function IWPlanningLeafletMap_header() { } function IWPlanningLeafletMap_call($attr) { // Generate the map template // Default attributes - can be overwritten from shortcode $attr = shortcode_atts(array( 'lat' => '50.675', 'lon' => '-1.32', 'id' => 'iwmap_1', 'zoom' => '11', 'width' => '800', 'height' => '500', 'markers'=>'' ), $attr); $html = '<div class="folium-map" id="'.$attr['id'].'" style="width: '. $attr['width'] .'px; height: '. $attr['height'] .'px"></div> <script type="text/javascript"> var base_tile = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 18, minZoom: 1, attribution: "Map data (c) OpenStreetMap contributors - http://openstreetmap.org" }); var baseLayer = { "Base Layer": base_tile } /* list of layers to be added */ var layer_list = { }; /* Bounding box. */ var southWest = L.latLng(-90, -180), northEast = L.latLng(90, 180), bounds = L.latLngBounds(southWest, northEast); /* Creates the map and adds the selected layers */ var map = L.map("'.$attr['id'].'", { center:['.$attr['lat'].', '.$attr['lon'].'], zoom: '.$attr['zoom'].', maxBounds: bounds, layers: [base_tile] }); L.control.layers(baseLayer, layer_list).addTo(map); //cluster group var clusteredmarkers = L.markerClusterGroup(); //section for adding clustered markers '; $markers = get_transient( 'iwcurrplanningitems' ); if ( false === $markers ) { $url='https://api.morph.io/psychemedia/iwplanningscraper/data.json?key=*****MORPHIOKEY****&query=select%20*%20from%20IWPLANNING%20where%20date(%22Consultation%20End%20Date_t%22)%3Edate(%22now%22)'; $json = file_get_contents($url); $markers=json_decode($json, true); set_transient('iwcurrplanningitems', $markers, 60*60*8); } for ($i = 0;$i < count($markers);$i ++){ $arrkeys=['Agent or Applicant','Location','Proposal']; foreach($arrkeys as $arrkey){ $markers[$i][$arrkey] = str_replace("\n", "<br/>", $markers[$i][$arrkey]); $markers[$i][$arrkey] = str_replace("\r", "<br/>", $markers[$i][$arrkey]); } $html .=' var marker_'.$i.'_icon = L.AwesomeMarkers.icon({ icon: "info-sign",markerColor: "blue",prefix: "glyphicon",extraClasses: "fa-rotate-0"}); var marker_'.$i.' = L.marker(['.$markers[$i]['lat'].','.$markers[$i]['lon'].'], {"icon":marker_'.$i.'_icon}); marker_'.$i.'.bindPopup("<strong>Consultation start:</strong> '.$markers[$i]['Consultation Start Date'].'<br/><strong>Consultation end:</strong> '.$markers[$i]['Consultation End Date'].'<br/><strong>Location:</strong> '.$markers[$i]['Location'].'<br/><em> '.$markers[$i]['Parish'].' parish, '.$markers[$i]['Ward'].' ward.</em><br/><strong>Proposal:</strong> '.$markers[$i]['Proposal'].'<br/><strong>Agent or Applicant:</strong> '.$markers[$i]['Agent or Applicant'].'<br/><strong>Case Officer:</strong> '.$markers[$i]['Case Officer'].'<br/><em><a href=\'https://www.iwight.com/planning/'.$markers[$i]['stub'].'\'>View application</a></em>"); marker_'.$i.'._popup.options.maxWidth = 300; clusteredmarkers.addLayer(marker_'.$i.'); //add the clustered markers to the group anyway map.addLayer(clusteredmarkers); '; } $html .= '</script>'; return $html; ?> <?php } add_shortcode('IWPlanningLeafletMap', 'IWPlanningLeafletMap_call'); ?>
One thing I started to wonder over the Christmas break was whether this approach could provide a way of sharing “data2text” content. For example, having a plugin that creates a canned summary of jobseeker’s allowance figures from data cached from the ONS website? A downside of this is that I’d have to write the data2text script using PHP, which means I couldn’t directly build on related code I’ve written previously…
I also wonder if we could use custom fields to permanently store data for a particular post. For example, we might check whether or not a custom field exists for the post, and if it doesn’t we could create and populate it using data pulled from an API, (possibly keyed by plugin/shortcode parameters, or the post publication date), using a WordPress add_post_meta() function call?