Extending Timber - wordpress

I'm using Timber for the first time, and I find that it is hard to convert my pure PHP thinking to this templating model.
What I'm confused about at the moment is using a date which is an input field, not the post date.
So I've got a repeater field from ACF. It has three sub-fields: release_date, document_file, and document_title. My release date format is YYYY-MM-DD.
As you might have guessed, this repeater field is output as a list of PDFs.
So far so good, but then I want some advanced functionality - namely, to have the PDFs display by release_date by default. I also want to be able to filter by the year, i.e. - if you click "2015", it would only show the documents from that year.
I know exactly how I would do this in straight WordPress, but I'm pretty confused making it on Timber. I've been trying to do it using a custom filter, but I have felt what I actually want is a custom class?
In addition, when I installed Timber it didn't come with the starter theme, so I did a search and downloaded one from GitHub. I've got a feeling this is a legacy version though, because the file structure and syntax doesn't seem to match the documentation.
Downloaded from here: https://github.com/timber/starter-theme
But for example this code in the starter theme functions.php:
function add_to_twig( $twig ) {
/* this is where you can add your own functions to twig */
$twig->addExtension( new Twig_Extension_StringLoader() );
$twig->addFilter('split_date', new Twig_SimpleFilter('split_date', array($this, 'split_date')));
return $twig;
}
Doesn't quite match the syntax in https://github.com/timber/timber/wiki/Extending-Timber under "Adding to Twig".

Here are a few helper functions to make it easy for me to extend timber.
function add_context_var( $key, $var ) {
add_filter( 'timber_context', function ( $context ) use ( $key, $var ) {
$context[ $key ] = $var;
return $context;
} );
}
function add_context_func( $key, $callback ) {
add_filter( 'timber/twig', function ( $twig ) use ( $key, $callback ) {
$twig->addFunction( new \Twig_SimpleFunction( $key, $callback ) );
return $twig;
} );
}
function add_to_context( $key, $val ) {
if ( is_callable( $val ) ) {
add_context_func( $key, $val );
} else {
add_context_var( $key, $val );
}
}
function add_to_context_filter( $key, $callback ) {
add_filter( 'get_twig', function ( $twig ) use ( $key, $callback ) {
$twig->addExtension( new Twig_Extension_StringLoader() );
$twig->addFilter( new Twig_SimpleFilter( $key, $callback ) );
return $twig;
} );
}
//php file
add_to_context("blue", "this key is blue")
add_to_context("red", function($extra = ""){
return "this key is red and it has $extra";
})
add_to_context_filter( "relative_link", function ( $content ) {
return str_replace( "http://", "//", $content );;
} );
//twig file
color: {{ blue }}
color: {{ red("a function parameter") }}
github: {{ "http://github.com/"|relative_link }}
//output html
color: this key is blue
color: this key is red and it has function parameter
github: //github.com/

Related

WordPress ACF how to filter a textarea field content

I'm building a WordPress theme that uses the Advanced Custom Fields (ACF plugin). I have the following function via functions.php:
function filter_p_tags( $content ) {
$content = str_replace( '<p>','<p class="custom__class">', $content );
return $content;
}
add_filter('the_content', 'filter_p_tags');
add_filter('acf_the_content','filter_p_tags');
The <p> tags via posts and pages are successfully being replaced with <p class="custom__class">. However, my ACF fields are not being filtered. What am I doing wrong here?
It's worth mentioning that the ACF fields in question belong to an options page. Here's how an option field looks within one of my templates.
<?php the_field( 'text', 'option' ); ?>
If your ACF field is a textarea, then you would want to use acf/format_value/type=textarea filter hook instead of using acf_the_content which would be applied on wysiwyg.
add_filter('acf/format_value/type=textarea', 'filter_p_tags_acf', 10, 3);
So your entire code would be something like this:
add_filter('the_content', 'filter_p_tags');
function filter_p_tags( $content ) {
$content = str_replace( '<p>','<p class="custom__class">', $content );
return $content;
}
add_filter('acf/format_value/type=textarea', 'filter_p_tags_acf', 10, 3);
function filter_p_tags_acf( $value, $post_id, $field ) {
$value = str_replace( '<p>','<p class="custom__class">', $value );
return $value;
}
Another way of doing this
Alternatively, as you suggested, we could use acf/format_value/key={$key} filter hook instead of using acf/format_value/type=textarea. Like so:
add_filter('acf/format_value/key=field_abc123456', 'filter_p_tags_acf', 10, 3);
function filter_p_tags_acf( $value, $post_id, $field ) {
$value = str_replace( '<p>','<p class="custom__class">', $value );
return $value;
}

WooCommerce override function

I'd like to override a function in woocommerce, specifically -
woocommerce/includes/wc-cart-functions.php (the wc_cart_totals_order_total_html function).
I could edit the function directly (it outputs html that needs to be changed), but I'd prefer not to as I'll lose changes on updates.
I'm sure it's a common thing, I'm just not quite sure how to go about doing that. If I copy the function to functions.php in my theme, I get an error about re-declaring the function.
It's an old topic, but maybe I could help a bit. I had similar problem. I wanted to override currencies, and add a custom currency. The functions are in woocommerce/includes/wc-core-functions.php
function get_woocommerce_currencies() {
return array_unique(
apply_filters( 'woocommerce_currencies',
array(
The other function is:
function get_woocommerce_currency_symbol( $currency = '' ) {
if ( ! $currency ) {
$currency = get_woocommerce_currency();
}
switch ( $currency ) {
...
return apply_filters( 'woocommerce_currency_symbol', $currency_symbol, $currency );
This is the code I've put in functions.php of my child theme:
add_filter( 'woocommerce_currencies', 'add_my_currency' );
function add_my_currency( $currencies ) {
$currencies['RSD'] = __( 'Serbian dinar', 'woocommerce' );
return $currencies;
}

Apply the_title filter to post titles AND backend auto social-sharing plugin, but not nav menu

The Challenge
I have been trying to write a filter that applies (prepends a string) to (a) post titles of a certain post type and (b) to that same post type WordPress admin backend for use by an auto social sharing plugin.
I've gotten really close. I've achieved:
1. prepending only to custom post_type post titles
2. prepending to custom_post type titles AND all nav menu items
3. prepending to ALL titles (post, page, nav menu, and backend auto-sharing plugin)
The final, 3rd attempt got me very hopeful, but yet still, no matter the function, conditional statement, or combination I use, I can't get custom_post title AND backend social sharing plugin, but not nav menu items.
My Code - what I've tried
I've tried different conditionals, like is_admin() and !is_nav_menu with all nav menu IDs. I've also tried comparing the ID of the post to the ID of the menu. Finally, I've also tried adding the filter only to loop_start and that seems promising if combined with other statements. I will clean up my code for your review but I leave it this way for now in hopes it helps to see what I've tried and maybe where I went wrong with any of those methods.
// , $id = NULL
function append_album_review_to_title( $title ) {
global $post;
global $dont_apply_title_filter;
$text = 'Album Review: ';
$prepended_title = $text . $title;
/*
$nav_menus_obj = wp_get_nav_menus();
$nav_menu_ids = '';
foreach( $nav_menus_obj as $nav_menu ) {
$nav_menu_ids .= $nav_menu->term_id . ',';
}
rtrim($nav_menu_ids, ',');
*/
// if ( get_post_type( $post->ID ) == 'album_review' && in_the_loop() ){
// if ( get_post_type( $post->ID ) == 'album_review' && $id == get_the_ID() ){
// if ( !is_nav_menu( '32057,32058,35135,32054,32056' ) ) {
// if ( get_post_type( $post->ID ) == 'album_review' && is_nav_menu( $id ) ) {
if ( get_post_type( $post->ID ) == 'album_review' && !$dont_apply_title_filter ) {
//print_r( $nav_menu_ids );
//print_r( is_nav_menu( $nav_menu_ids ) );
return $prepended_title;
/*
} elseif ( get_post_type( $post->ID ) == 'album_review' ) {
return $title;
*/
} else {
return $title;
};
}
add_filter('the_title', 'append_album_review_to_title');
//add_action('save_post', 'custom_post_type_title', 100);
/*
function set_custom_title() {
add_filter( 'the_title', 'append_album_review_to_title', 10, 2 );
}
add_action( 'loop_start', 'set_custom_title' );
*/
Solution
function append_album_review_to_title( $title, $id = NULL ) {
if ($id) {
if ( get_post_type( $id ) == 'album_review' ){
return 'Album Review: ' . $title;
} else {
return $title;
}
} else {
return 'Album Review: ' . $title;
};
}
add_filter('the_title', 'append_album_review_to_title', 10, 2);
Explanation
First trying this:
function append_album_review_to_title( $title, $id ) {
if ( get_post_type( $id ) == 'album_review' ){
return 'Album Review: ' . $title;
} else {
return $title;
}
}
add_filter('the_title', 'append_album_review_to_title', 10, 2);
I noticed that on the backend, the social sharing plugin I am using to auto-share posts would return warnings like missing parameter 2 and Notice: Trying to get property of non-object..., and so it occurred to me that unlike the front end (nav menu and the loop), in the backend, apparently an $id is not being passed to the_title and so I can use this as a conditional.
Checking for $id will, for my purposes, tell me that if it is true, we are on front end, if it is false, then we are at backend post view.
This code accomplishes what I need (ie, modify post title in the loop, do NOT modify nav menu items, and modify the_title for use by a backend social sharing plugin):

WordPress add metadata to custom column in posts list

I have added a custom column 'rating' to wordpress posts list (in the admin):
function test_modify_post_table( $column ) {
$column['rating'] = 'Rating';
return $column;
}
add_filter( 'manage_posts_columns', 'test_modify_post_table' );
Is there any way to now populate it with metadata? I've searched all over, tried quite a few examples, but nothing seems to be working for me . Thanks!
You can use the following:
add_action( 'manage_posts_custom_column' , 'rating_columns' );
function rating_columns( $column ) {
global $post;
switch ( $column ) {
case 'Rating':
$metaData = get_post_meta( $post->ID, 'METADATA_NAME', true );
echo $metaData;
break;
}
}
Just replace METADATA_NAME with the name of your ratings metadata

adding custom page template from plugin

I'm working on building my first plugin for wordpress and am needing it to dynamically add a custom page for a login screen among other things.
The only thing I've been able to find that's anywhere near what I'm needing is here: WP - Use file in plugin directory as custom Page Template? & Possible to add Custom Template Page in a WP plugin?, but they're still not quite what I'm looking for.
Here is the code that I currently have running in my plugin...
// Add callback to admin menu
add_action( 'template_redirect', 'uploadr_redirect' );
// Callback to add menu items
function uploadr_redirect() {
global $wp;
$plugindir = dirname( __FILE__ );
// A Specific Custom Post Type
if ( $wp->query_vars["post_type"] == 'uploadr' ) {
$templatefilename = 'custom-uploadr.php';
if ( file_exists( TEMPLATEPATH . '/' . $templatefilename )) {
$return_template = TEMPLATEPATH . '/' . $templatefilename;
} else {
$return_template = $plugindir . '/themefiles/' . $templatefilename;
}
do_theme_redirect( $return_template );
}
}
function do_theme_redirect( $url ) {
global $post, $wp_query;
if ( have_posts ()) {
include( $url );
die();
} else {
$wp_query->is_404 = true;
}
}
Using this would require that my client create new page... what I'm needing is for the pluging to auto create a custom page (with a customized path, meaning .com/custompathhere) using a template file from the plugin folder, which will then contain all actions the plugin performs.
Note: This plugin is designed to run on one page, therefore reducing load-time and etc.
Thanks in advance!
Here is my code solution for adding page templates from a Wordpress plugin (inspired by Tom McFarlin).
This is designed for a plugin (the template files are searched for in the root directory of the plugin). These files are also in exactly the same format as if they were to be included directly in a theme. This can be changed if desired - check out my full tutorial http://www.wpexplorer.com/wordpress-page-templates-plugin/ for greater detail on this solution.
To customise, simply edit the following code block within the __construct method;
$this->templates = array(
'goodtobebad-template.php' => 'It\'s Good to Be Bad',
);
Full code;
class PageTemplater {
/**
* A Unique Identifier
*/
protected $plugin_slug;
/**
* A reference to an instance of this class.
*/
private static $instance;
/**
* The array of templates that this plugin tracks.
*/
protected $templates;
/**
* Returns an instance of this class.
*/
public static function get_instance() {
if( null == self::$instance ) {
self::$instance = new PageTemplater();
}
return self::$instance;
}
/**
* Initializes the plugin by setting filters and administration functions.
*/
private function __construct() {
$this->templates = array();
// Add a filter to the attributes metabox to inject template into the cache.
add_filter(
'page_attributes_dropdown_pages_args',
array( $this, 'register_project_templates' )
);
// Add a filter to the save post to inject out template into the page cache
add_filter(
'wp_insert_post_data',
array( $this, 'register_project_templates' )
);
// Add a filter to the template include to determine if the page has our
// template assigned and return it's path
add_filter(
'template_include',
array( $this, 'view_project_template')
);
// Add your templates to this array.
$this->templates = array(
'goodtobebad-template.php' => 'It\'s Good to Be Bad',
);
}
/**
* Adds our template to the pages cache in order to trick WordPress
* into thinking the template file exists where it doens't really exist.
*
*/
public function register_project_templates( $atts ) {
// Create the key used for the themes cache
$cache_key = 'page_templates-' . md5( get_theme_root() . '/' . get_stylesheet() );
// Retrieve the cache list.
// If it doesn't exist, or it's empty prepare an array
$templates = wp_get_theme()->get_page_templates();
if ( empty( $templates ) ) {
$templates = array();
}
// New cache, therefore remove the old one
wp_cache_delete( $cache_key , 'themes');
// Now add our template to the list of templates by merging our templates
// with the existing templates array from the cache.
$templates = array_merge( $templates, $this->templates );
// Add the modified cache to allow WordPress to pick it up for listing
// available templates
wp_cache_add( $cache_key, $templates, 'themes', 1800 );
return $atts;
}
/**
* Checks if the template is assigned to the page
*/
public function view_project_template( $template ) {
global $post;
if (!isset($this->templates[get_post_meta(
$post->ID, '_wp_page_template', true
)] ) ) {
return $template;
}
$file = plugin_dir_path(__FILE__). get_post_meta(
$post->ID, '_wp_page_template', true
);
// Just to be safe, we check if the file exist first
if( file_exists( $file ) ) {
return $file;
}
else { echo $file; }
return $template;
}
}
add_action( 'plugins_loaded', array( 'PageTemplater', 'get_instance' ) );
Check out my tutorial on this for more info.
http://www.wpexplorer.com/wordpress-page-templates-plugin/
I hope this helps you in what you want to do :)
I actually was able to talk to a developer friend of mine after revising the code quite a bit.
Here it is...
<?php
register_activation_hook( __FILE__, 'create_uploadr_page' );
function create_uploadr_page() {
$post_id = -1;
// Setup custom vars
$author_id = 1;
$slug = 'event-photo-uploader';
$title = 'Event Photo Uploader';
// Check if page exists, if not create it
if ( null == get_page_by_title( $title )) {
$uploader_page = array(
'comment_status' => 'closed',
'ping_status' => 'closed',
'post_author' => $author_id,
'post_name' => $slug,
'post_title' => $title,
'post_status' => 'publish',
'post_type' => 'page'
);
$post_id = wp_insert_post( $uploader_page );
if ( !$post_id ) {
wp_die( 'Error creating template page' );
} else {
update_post_meta( $post_id, '_wp_page_template', 'custom-uploadr.php' );
}
} // end check if
}
add_action( 'template_include', 'uploadr_redirect' );
function uploadr_redirect( $template ) {
$plugindir = dirname( __FILE__ );
if ( is_page_template( 'custom-uploadr.php' )) {
$template = $plugindir . '/templates/custom-uploadr.php';
}
return $template;
}
?>
I'm providing a general solution for those that want to add a template to a post from the their plugin. Use the single_template filter.
<?php
add_filter( 'single_template', 'add_custom_single_template', 99 );
function add_custom_single_template( $template ) {
return plugin_dir_path( __FILE__ ) . 'path-to-page-template-inside-plugin.php';
}
?>
Also, if you want to use the template in a specific post type, then:
<?php
add_filter( 'single_template', 'add_custom_single_template', 99 );
function add_custom_single_template( $template ) {
if ( get_post_type() == 'post-type-name'; ) {
return plugin_dir_path( __FILE__ ) . 'path-to-page-template-inside-plugin.php';
}
return $template;
}
?>

Resources