Timeline Express Pro Modifications
Timeline Express Pro is a WordPress Plugin that presents information in a timeline format. They refer to the individual timeline items as "announcements", which are similar to post or pages. By default, instead of displaying the full content of the "announcement" it displays an automatically generated "excerpt" (abbreviated version). By default this excerpt does not contain any HTML or ShortCode (only plain text). For people that do wish to display HTML in the "excerpts" they created an Add-On (Timeline Express HTML Excerpt Add On) that displays a custom "excerpt" of the timeline post which can include HTML content and ShortCode. Unfortunately this is a manual process. IE, the "excerpt" must be created and maintained separately from the main announcement.
That's the way it functions, and I used the features as they were to do something slightly different than what they had envisioned for their software. I used the custom "excerpt" as the main content of my timeline. I did this because I realized I could make each "announcement" its own self-contained webpage. And it worked great. But it also left another feature unneeded. That feature was the ability to display the full announcement. That wasn't something I needed because I was already displaying the full "announcement" with the Excerpt Add-On.
But then I came up with a use for it. The website I was developing was a biography site that required sources to be cited. I changed the name of the link at the bottom of each timeline item to "Sources & Additional Information". The result was I could write the main content in the Excerpt Add-On section and do the citations in the "attachment" section, all on one screen. Perfect. Convenient and it displayed everything just like I wanted it to be.
Everything was perfect until it came to SEO (Search Engine Optimization) that is. The SEO Plugin I was using only provided a sitemap for the full "announcement", not the content of the Excerpt Add-On. That meant only the reference sections were cataloged by search engines, not the content that was displayed to users. I thought I could work around it by having the SEO Plugin create sitemaps for each of the webpages the timelines were on. That didn't work either because the SEO software couldn't "see" the individual items on a timeline. So a page containing several dozen images resulted in a sitemap that had ZERO images. Crap!
How to fix this?
The Fix(es)
The first thing that needed to be done was to swap everything in the "announcement" section with the content in the Excerpt Add-On section. I wasn't about to do a copy past on that for several hundred entries. The solution to that was to swap column data in two database tables with each other. But as I read, mySQL (MariaDB) doesn't have that capability like other database servers do. for mySQL, it has to be done in stages. I read a bunch of stuff from people who were experts in SQL server. For my purposes it was way to complex. To stay within my abilities, I decided to create a couple of temporary tables and copy the data to them, and then copy from the temporary tables to the places I wanted to put the data. To make things even easier, I used phpMyAdmin.
"Swap" Table Data in the WordPress Database
- First, backup the database: Select Database in phpMyAdmin, Export Tab, use defaults, Go and save to a location
- During the above step I noticed that the database had the incorrect collation defined, so I changed it to the WordPress recommended utf8mb4_unicode_ci setting: Select Database in phpMyAdmin, Operations Tab, Collation Section
- Create two temporary tables, TT1 and TT2 (as in Temporary Table 1 and 2): Select Database in phpMyAdmin, immediately under it select New, and create three columns with the following settings
- TT1 (duplicating the necessary columns from the WordPress postmeta table);
- Name: post_id, Type: bigint, Length/Values: 20
- Name: meta_key, Type: varchar, Length/Values: 255
- Name: meta_value, Type: longtext, , Length/Values: Leave it Blank
- Save it
- Create an Index on Column 1: Go, Index Choice: PRIMARY
- TT2 (duplicating the necessary columns from the WordPress posts table);
- Name: ID, Type: bigint, Length/Values: 20
- Name: post_type, Type: varchar, Length/Values: 20
- Name: post_content, Type: longtext, Length/Values: Leave it Blank
- Save it
- Create an Index on Column 1: Go, Index Choice: PRIMARY
- TT1 (duplicating the necessary columns from the WordPress postmeta table);
- Use a SQL statement to copy the data from the postmeta Table to TT1 Table: Select Database in phpMyAdmin, SQL Tab, Go, with the following SQL statement;
INSERT INTO TT1 (post_id, meta_key, meta_value)
SELECT post_id, meta_key, meta_value
FROM wp_postmeta
WHERE meta_key = 'announcement_custom_excerpt';
- Use a SQL statement to copy the data from the posts Table to TT2 Table: Select Database in phpMyAdmin, SQL Tab, Go, with the following SQL statement;
INSERT INTO TT2(ID, post_content, post_type)
SELECT ID, post_content, post_type
FROM wp_posts
WHERE post_type = 'te_announcements';
(The number of rows copied into TT1 and TT2 should be the same)
- Use a SQL statement to copy the data from the TT1 Table to the posts Table: Select Database in phpMyAdmin, SQL Tab, Go, with the following SQL statement;
UPDATE wp_posts INNER JOIN TT1
SET wp_posts.post_content = TT1.meta_value
WHERE TT1.post_id = wp_posts.ID;
- Use a SQL statement to copy the data from the TT2 Table to the postmeta Table: Select Database in phpMyAdmin, SQL Tab, Go, with the following SQL statement;
UPDATE wp_postmeta INNER JOIN TT2
SET wp_postmeta.meta_value = TT2.post_content
WHERE TT2.ID = wp_postmeta.post_id AND wp_postmeta.meta_key = 'announcement_custom_excerpt';
Editing of Excerpt Add-On PHP Code
After hunting around in the PHP code for a couple of minutes I found the areas where I needed to make the change. The change was to have the Excerpt Add-On retrieve data from the post Table instead of the postmeta Table. Below is the original code from the ../wp-content/plugins/timeline-express-html-excerpt-add-on/timeline-express-html-exerpts-add-on.php File (with tabs and spacing removed);
public function replace_default_timeline_express_excerpt( $excerpt, $post_id ) {
// If the new custom excerpt is set, return it.
if ( get_post_meta( $post_id, 'announcement_custom_excerpt', true ) ) {
echo apply_filters( 'the_content', get_post_meta( $post_id, 'announcement_custom_excerpt', true ) );
return;
}
return $excerpt;
}
The line with: echo apply_filters( 'the_content', get_post_meta( $post_id, 'announcement_custom_excerpt', true ) ); was replaced with the below two lines of code;
$AlternateSourceOfContent = get_post ( $post = $post_id, $output = 'OBJECT', $filter = 'raw');
echo apply_filters( 'the_content', $AlternateSourceOfContent->post_content );
(The section of code on the first line to the right of the equal sign could have just been: get_post( $post_id ); but the above is the full syntax of the WordPress "get_post" function)
OK, that solves the SEO issue because the sitemap now recognizes all the individual items and images because they also have their own individual URLs and "slugs". And what is displayed to users is same as before with no changes. But unfortunately the above "fix(es)" breaks the "Sources & Additional Information" link as it takes users to the full URL of the "announcement". The citations are in the Excerpts Add-On section. It's still easy to edit, with both the "announcement" and "excerpt" (AKA citations and references in my case), but now there needs to be a way to display the citations and references.
Fixing the Citations and References Link
Ideally it would be great to have a modal popup dialogue that displays the Citations and References, which involves extracting the information from the postmeta table and displaying it in a popup. That's the easy part. Getting the data in the postmeta table to display properly was a problematic issue. The main issue stems from the fact there is HTML and ShortCode in postmeta table. It needs to be displayed as raw output from WordPress, not as text in a paragraph.
Here's some foreshadowing: A Popup window as the solution? As it turns out, no way. Why? Because every single popup Plugin seems to load all of its information with the current page. If there's a bunch of information to load, as there is with a timeline that has a lot of events and a lot of sources that reference hundreds of books and web sites, that will kill a pages performance. All of that information did not need to load until it was called upon.
As it turns out, a Lightbox was the solution. Yes, traditionally those plugins are for images or galleries of images. But some can do iFrames. The best one I found was Featherlight. It can be configured to host / load images, videos, HTML, and iFrames. Perfect!
Transfer Information into a Link for Clients to Click on
The easy way to move information and access information between pages or the server and browser is to append data to the end of a URL: https://WhatEverWebSite/WhatEverPage.php?WhatEverVariable=1234567890 and access it with GET. But how can that be done. Easy, just build it into the string passed to the client using PHP.
Do it with a GET Command
The below code is a modified version of code from the Timeline Express Web site and should be put in the site Theme's (or child theme's) functions.php file;
function generate_custom_read_more_link()
{
global $post;
// "Gets" the ID of the current post from the POSTS Table
$post_id_to_pass = get_the_ID();
// Original line: echo wp_kses_post( '<a href="' . get_the_permalink( $post->ID ) . '" class="custom-class">Read More</a>' );
echo '<a href="' . 'WhatEverVariableName?metapost_id=' . $post_id_to_pass . '"class="WhatEverClassName">WhatEverLinkName</a>';
// Keep in mind, depending on how one's WordPress site is configured, the above HREF may need a leading / (forward slash) in the title), IE '/WhatEverVariableName?..."
}
add_action( 'timeline-express-after-excerpt', 'generate_custom_read_more_link' );
Do it with a POST Command
But it is so ugly. Why not pass the information via Headers instead? Replace the echo command in the above code block with this one;
echo '<a href="" class="WhatEverClassName" onclick="event.preventDefault(); navigate_to_this_url(' . $post_id_to_pass . '); return false;">WhatEverLinkName</a>';
It also requires some JavaScript on the client side which can be inserted with the below PHP code. And as above, it goes into the functions.php file;
function add_this_script_to_webpage_header()
{
?>
<script type="text/javascript">
function navigate_to_this_url(the_post_id)
{
document.body.innerHTML = '<form id="dynForm" action="WhatEverURL" method="post"><input type="hidden" name="PostIdPassed" value=' + the_post_id + '></form>';
document.getElementById("dynForm").submit();
return false;
}
</script>
<?php
add_action('wp_head', 'add_this_script_to_webpage_header');
The odd ?> and <?php Elements are there to put a break in the PHP code and allow the straight HTML to be injected into the page. This could be done more neatly with some sort of dedicated PHP function to inject HTML instead of just slopping it into the middle of the PHP code, but it works. But why not make it better an neater with the below code;
function add_this_script_to_webpage_header()
{
$script_string = '<script type="text/javascript">' .
'function navigate_to_this_url(the_post_id)' .
'{' .
"document.body.innerHTML = '<form id=\"dynForm\" action=\"https://WhatEverURL\" method=\"post\"><input type=\"hidden\" name=\"PostIdPassed\" value=' + the_post_id + '></form>';" .
'document.getElementById("dynForm").submit();' .
'return false' .
'}' .
'</script>';
echo $script_string;
}
add_action('wp_head', 'add_this_script_to_webpage_header');
All of the odd \ (backslashes) in the $script_string code is the escape character for PHP. And there's also one more item to configure when using the POST method. A dedicated page (WhatEverURL) needs to be created. And the below code needs to be inserted to in order to only have the functionality run on the pages it is intended to run on;
// This If Function is TRUE if the requested Page is for the intended links
// When FALSE, the $CONTENT is simply returned as is with no changes
if ( basename(get_permalink()) == 'PageNameToCheck') // If the Page or Post name changes, it also needs to be changed in the link generated in the Timeline Express function above.
{
$post_id_from_post_header = $_POST['PostIdPassed'];
$post_id_from_get = $_GET['metapost_id'];
// This If Function is TRUE if the page was requested via a direct link or crawled by a robot.
if( is_null( $post_id_from_post_header ) )
{
$post_id_from_post_header = $post_id_from_get;
}
$content_of_custom_excerpt = get_post_meta( $post_id_from_post_header, 'announcement_custom_excerpt', true );
// This If Function is only TRUE if there is no announcement_custom_exerpt Field in the metapost Table for a post.
// When FALSE, an announcement_custom_exerpt Field in the metapost Table for a post exists.
if( is_null( $content_of_custom_excerpt ) )
{
$custom_excerpt_result_message = 'This post has no custom excerpt associated with it.';
$content = $content;
return $content;
}
else{
$content = 'Post ID from GET:' . $post_id_from_get . '<br><br>' . 'Post ID from HEADER:' . $post_id_from_post_header . $content_of_custom_excerpt;
return $content;
}
}
else{
$content = $content;
return $content;
}
}
add_filter ('the_content', 'display_sources_and_additional_information');
Forget the Above Methods and use a Lightbox
But either of the two methods opens a new webpage. That seems a bit clunky. Why not do something far more slick like a Popup? Well, as mentioned above there are issues with that. And also mentioned above is a better solution. Use a lightbox. After installing the Featherlight Lightbox, insert the below PHP code into the theme's (or child theme's) functions.php file;
function generate_custom_read_more_link()
{
global $post;
// "Gets" the ID of the current post from the POSTS Table
$post_id_to_pass = get_the_ID();
// Original line: echo wp_kses_post( '<a href="' . get_the_permalink( $post->ID ) . '" class="custom-class">Read More</a>' );
echo '<a href="' . 'WhatEverRelativeURL?metapost_id=' . $post_id_to_pass . '" data-featherlight="iframe" data-featherlight-iframe-width="2000" data-featherlight-iframe-height="800" class="FeatherLightUsesThisURLsoNameItAppropriately">WhatEverTextOrLinkName</a>';
}
add_action( 'timeline-express-after-excerpt', 'generate_custom_read_more_link' );