ACF blocks not showing with Timber - wordpress

So I am getting my head around how Timber works, I'm getting the hang of that so now I want to now get my head around ACF and Twig.
Ive added the following to functions.php:
add_action( 'acf/init', 'my_acf_init' );
function my_acf_init() {
// Bail out if function doesn’t exist.
if ( ! function_exists( 'acf_register_block' ) ) {
return;
}
// Register a new block.
acf_register_block( array(
'name' => 'example_block',
'title' => __( 'Example Block', 'your-text-domain' ),
'description' => __( 'A custom example block.', 'your-text-domain' ),
'render_callback' => 'my_acf_block_render_callback',
'category' => 'formatting',
'icon' => 'admin-comments',
'keywords' => array( 'example' ),
) );
}
/**
* This is the callback that displays the block.
*
* #param array $block The block settings and attributes.
* #param string $content The block content (emtpy string).
* #param bool $is_preview True during AJAX preview.
*/
function my_acf_block_render_callback( $block, $content = '', $is_preview = false ) {
$context = Timber::context();
// Store block values.
$context['block'] = $block;
// Store field values.
$context['fields'] = get_fields();
// Store $is_preview value.
$context['is_preview'] = $is_preview;
// Render the block.
Timber::render( 'block/example-block.twig', $context );
}
This is the block file:
{#
/**
* Block Name: Example block
*
* This is the template that displays the example block.
*/
#}
{% if is_preview %}
<p>I will only appear in the editor.</p>
{% endif %}
<div id="example-{{ block.id }}" class="wrapper">
<h1>{{ fields.title }}</h1>
<p>{{ fields.description }}</p>
</div>
<style type="text/css">
#testimonial-{{ block.id }} {
background: {{ fields.background_color }};
color: {{ fields.text_color }};
}
</style>
Where am I going wrong? I have searched loads of documentation but I haven’t found anything and I'm starting to pull my hair out over it as it should be easy

OK so this probably isn’t the best or maybe even correct way but I have finally got the ACF blocks to show on the frontend - yay!
I added the following to my fuctions.php:
add_filter('timber_context', 'bt_timber_add_to_context');
function bt_timber_add_to_context ($context) {
// add current page content
$context['page'] = Timber::get_post();
return $context;
}
Whilst I would love to say I came up with the solution myself all credit goes to #Buttered_Toast (found a solution

Related

How to access advanced custom field values within a gutenberg block?

I have a custom post type with a few advanced custom fields. I'm trying to access these custom field values from within a Gutenberg block.
I've added the following to my register_post_type function
'show_in_rest' => true,
'supports' => array( 'title', 'editor', 'custom-fields' ),
I can successfully retrieve the custom posts from within my Gutenberg block using:
select('core').getEntityRecords('postType', 'customType')
but I'm not seeing the custom fields or their values.
Here's my block's JavaScript:
const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;
const { withSelect } = wp.data;
registerBlockType('cgb/block-press-block', {
title: __('Press Block'),
icon: 'awards',
category: 'common',
keywords: [
__('press-block'),
],
edit: withSelect((select) => {
return {
posts: select('core').getEntityRecords('postType', 'press')
};
})(({posts}) => {
return <p>Content</p>;
}),
});
Is there a way to access the custom post's advanced field values on the editor side or a way to pass that data to the block?
As you're using Advanced Custom Fields already, are you able to rather than registering your own block independently, use acf_register_block instead? That way you can access fields from ACF in PHP based templates.
Here are some useful links about this:
ACF 5.8 – Introducing ACF Blocks for Gutenberg
acf_register_block()
This code is taken from the ACF blog post above and posted here for completeness in case the above link changes.
Register the ACF block:
add_action('acf/init', 'my_acf_init');
function my_acf_init() {
// check function exists
if( function_exists('acf_register_block') ) {
// register a testimonial block
acf_register_block(array(
'name' => 'testimonial',
'title' => __('Testimonial'),
'description' => __('A custom testimonial block.'),
'render_callback' => 'my_acf_block_render_callback',
'category' => 'formatting',
'icon' => 'admin-comments',
'keywords' => array( 'testimonial', 'quote' ),
));
}
}
A callback function to include your block template:
function my_acf_block_render_callback( $block ) {
// convert name ("acf/testimonial") into path friendly slug ("testimonial")
$slug = str_replace('acf/', '', $block['name']);
// include a template part from within the "template-parts/block" folder
if( file_exists( get_theme_file_path("/template-parts/block/content-{$slug}.php") ) ) {
include( get_theme_file_path("/template-parts/block/content-{$slug}.php") );
}
}
The HTML of your block:
<?php
/**
* Block Name: Testimonial
*
* This is the template that displays the testimonial block.
*/
// get image field (array)
$avatar = get_field('avatar');
// create id attribute for specific styling
$id = 'testimonial-' . $block['id'];
// create align class ("alignwide") from block setting ("wide")
$align_class = $block['align'] ? 'align' . $block['align'] : '';
?>
<blockquote id="<?php echo $id; ?>" class="testimonial <?php echo $align_class; ?>">
<p><?php the_field('testimonial'); ?></p>
<cite>
<img src="<?php echo $avatar['url']; ?>" alt="<?php echo $avatar['alt']; ?>" />
<span><?php the_field('author'); ?></span>
</cite>
</blockquote>
<style type="text/css">
#<?php echo $id; ?> {
background: <?php the_field('background_color'); ?>;
color: <?php the_field('text_color'); ?>;
}
</style>
This creates a basic testimonials block as a simple starting point. ACF handles the JavaScript handling within Gutenberg so all you have to do is worry about the PHP side of things.
That means you get to use get_field() and the_field() function like we're (ACF fans) are used to. Mixing ACF and Gutenberg without using this native way may cause headaches and possibly require a plugin to access fields via the WordPress REST API.
Note: ACF support for Gutenberg blocks requires ACF version 5.8 or higher.

Add placeholder or empty value to Entity Type with multiple = true in Symfony

I want to do this:
$builder->add('participants', EntityType::class, array(
'label' => 'Teilnehmer',
'class' => SchoolUser::class,
'multiple' => true,
'choices' => $this->getParticipantsOfEntry($builder),
'empty_value' => 'All',
'empty_data' => null,
'preferred_choices' => array(null)
));
But I get no selected 'All' - field at all. This should not be hard, I wonder where is my mistake?
'placeholder' = 'All',
did also not work for me.
How can I do this?
I know the question was 4-months ago but I ran into a similar problem so thought I would share my solution in case it helps others.
First in the Form object:
class MealFormType extends AbstractType {
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder->add( 'courses', EntityType::class, array(
'class' => MealCourse::class,
'multiple' => true,
) );
$builder->addEventListener( FormEvents::PRE_SUBMIT, function( FormEvent $event ) {
// Remove added entries here... maybe something like
$data = $event->getData();
if( ($key = array_search( 'all', $data['courses'] ) ) !== false ) {
unset( $data['courses'][$key] );
$event->setData( $data );
}
} );
}
public function configureOptions( OptionsResolver $resolver ) {
$resolver->setDefaults( ['data_class' => Meal::class] );
}
public function finishView( FormView $view, FormInterface $form, array $options) {
$newChoice = new ChoiceView( null, 'all', 'I want them all!' );
array_unshift( $view->children['courses']->vars['choices'], $newChoice );
}
}
This creates the form with multiple meal courses that can be selected, such as appetizer, soup, salad, entree, coffee, desert, ... using the buildForm() method. Also in this method an event listener for the PRE_SUBMIT event is added whose job it will be to remove any choices we have added since they are probably not real (if they were, we wouldn't have to add them in this painful way).
It sets the data_class as usual in configureOptions().
Lastly it provides the finishView() method that creates a new choice with value of 'all' that will display as 'I want them all' in the select, then adds it at the beginning for the 'choices' array for the 'courses' form entry (this would be $view->children['courses']->vars['choices'][] = $newChoice; to put it at the end).
Ok, this is fine but it really doesn't do anything more than having an additional option to click or unclick. To use this we need some Javascript to manage the form. I just stuck this Javascript in the bottom of my Twig file and use jQuery.
The Twig file:
{% block body %}
{{ form_start(mealForm) }}
{{ form_row(mealForm.courses,
{'attr': {'class': 'js-meal_course-select'}}) }}
<button type="submit">Save</button>
{{ form_end(mealForm) }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
var mealCourseAllSelected = false;
jQuery(document).ready( function() {
// This will be called whenever an entry is selected or deselected
$('.js-meal_course-select').change( function() {
var selectedSet = $(this).val();
if( mealCourseAllSelected &&
(selectedSet.length != $(this).get(0).options.length - 1 ||
selectedSet.includes( 'all' )) ) {
var opts = $(this).get(0).options;
var len = opts.length;
for( var i = 0; i < len; ++i ) {
if( opts[i].value == 'all' ) opts[i].selected = false;
}
mealCourseAllSelected = false;
} else if( selectedSet.includes( "all" ) ||
selectedSet.length == $(this).get(0).options.length - 1 ) {
$('.js-meal_course-select option').prop( 'selected', true );
mealCourseAllSelected = true;
}
}
);
</script>
{% endblock %}
The JavaScript responds to changes to what is selected. If the select option with the value of 'all' is in the selected set, then all the select options are marked as selected, and a flag is set to say so. This is also done if everything is selected except the 'all' entry because that is kind of the definition of all.
If the 'all are selected' flag is set and there is a change, then that change must be deselecting at least one, unless that one is the select option with the value of 'all', it deselects the selection option with the value of 'all' and clears the 'all are selected' flag. If all are selected and the user tries to unselect just the select option with the value of 'all', it is just reselected.

Drupal7 | Custom Module | How to Display Template?

I'm having trouble figuring out how to display a template that lives inside of my custom module.
This is what I have:
<?php
function brl_footer_theme($existing, $type, $theme, $path) {
$theme = array();
$theme['brl_footer'] = array(
'render element' => 'content',
'template' => 'brl-footer',
'path' => drupal_get_path('module', 'brl_footer'),
);
return $theme;
}
/**
* Implements hook_block_info().
*/
function brl_footer_block_info() {
$blocks = array();
$blocks['brl_footer'] = array(
'info' => t('Custom Footer'),
);
return $blocks;
}
I have a template file in the module called brl-footer.tpl.php
It contains very basic HTML:
<h1>here's some content</h1>
Is it possible to display my template through the custom block 'brl_footer' that's being created?
The custom block is active and has been assigned to the proper region.
Any help on this would be hugely appreciated -
You'll need to implement hook_block_view to define what gets displayed in your block.
Also, if your template is just static content, you don't need to specify a "render element" or "variables" for your theme hook (though you could still make variables in a preprocess function).

Nested menu with parameters in Symfony and KNPmenu

I am struggling with Symfony2 and Knpmenu to build a menu that handles:
breadcrumbs
routing with dynamic parameters
rendering of separate menus starting with different children
My Menu/Builder.php file looks like this (the extra bits like navbar, pull-nav etc are part of the mopa_bootstrap extension that handles the rendering using bootstrap classes):
namespace My\AppBundle\Menu;
use Knp\Menu\FactoryInterface;
class Builder
{
public function mainMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem(
'root', array(
'navbar' => true,
'pull-right' => true,
)
);
// Main Menu -> Config
// no link here, it's just a placeholder
$dropdown = $menu->addChild(
'Config', array(
'dropdown' => true,
'caret' => true,
)
);
// Menu -> Config -> User
$dropdown2 = $dropdown ->addChild(
'User', array(
'route' => 'user',
)
);
// Secondary Menu -> Edit (but child of Menu -> Config -> User)
$dropdown2->addChild(
'Edit',
array(
'route' => 'user_edit',
'routeParameters' => array('name' => $options['id']),
)
);
The idea is to have a main menu that prints the first two levels only, and a separate menu that gets rendered somewhere else to allow users moving between the edit/delete/whatever views of that specific element being viewed.
What I am trying to achieve, is to have a single structure thus to handle the parenting structure, not only to have sign parents as active in the menu, but also to be able to handle a working breadcrumb structure.
InResources/views/base.html.twig I am calling the main menu like this:
{{ mopa_bootstrap_menu('MyAppBundle:Builder:mainMenu', {depth: 2}) }}
And ideally the sub menu like this:
{% set id = app.request.attributes.get('id') %}
{% if app.request.attributes.get('_route') starts with 'user_' %}
{% set menu = knp_menu_get('MyAppBundle:Builder:mainMenu', ['User'], {'id': id }) %}
{{ knp_menu_render(menu) }}
{% endif %}
However:
knpmenu returns error when rendering the main menu, as $options['id'] isn't defined
I still can't render the secondary menu (therefore by passing the parameter 'User') - the page just returns a black output in that block
Is this approach correct?
I'm using "knplabs/knp-menu": "2.0.*#dev" and "symfony/symfony": "2.5.*"
As it turns out, using this method allowed to access effectively $request and add the conditional in the MenuBuilder.php file to prompt the sub-sub-items only under certain conditions.
The final code looks like this:
// Menu/MenuBuilder.php
namespace My\AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\Request;
class MenuBuilder
{
private $factory;
/**
* #param FactoryInterface $factory
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
public function createMainMenu(Request $request)
{
$menu = $this->factory->createItem(
'root', array(
'navbar' => true,
'pull-right' => true,
)
);
// Main Menu -> Config
// no link here, it's just a placeholder
$dropdown = $menu->addChild(
'Config', array(
'dropdown' => true,
'caret' => true,
)
);
// Menu -> Config -> User
$dropdown2 = $dropdown ->addChild(
'User', array(
'route' => 'user',
)
);
// Secondary Menu -> Edit (but child of Menu -> Config -> User)
// Skip this part if we are not in the relevant page, thus to avoid errors
if (false !== strpos($request->get('_route'), 'user_') && $request->get('id')):
$dropdown2->addChild(
'Edit',
array(
'route' => 'user_edit',
'routeParameters' => array('id' => $request->get('id')),
)
);
endif;
And the services configuration file:
// Resources/config/services.yml
services:
my_app.menu_builder:
class: My\AppBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory"]
my_app.menu.main:
class: Knp\Menu\MenuItem
factory_service: my_app.menu_builder
factory_method: createMainMenu
arguments: ["#request"]
scope: request
tags:
- { name: knp_menu.menu, alias: main }
And then the Resources/views/base.html.twig template:
// Main menu
{% set menu = knp_menu_get('main', ['Config', 'Users'], {'id': id }) %}
{{ knp_menu_render(menu) }}
// Secondary menu
{% if app.request.attributes.get('_route') starts with 'user_' %}
{% set menu = knp_menu_get('main', ['Config', 'Users'], {'id': id }) %}
{{ mopa_bootstrap_menu(menu, {'automenu': 'navbar'}) }}
{% endif %}
The solution seems to work, but if you have a better approach please do let me know!

Symfony2 - How to you list one specific post tag instead of all of them?

Stumped on this. I want to link my post tags so that when it's clicked it lists all post to that tag, similar to a tag cloud---adding that functionality to the individual tags under the post.
Currently, I have it set it up to use the same function as my tag cloud but when I hover over one tag it shows all tags as I've used the blog results (blog.tags) in the for loop. See screen shot: (hovering over one tag shows all tags in the blog post)
When I use a get all tags and a for loop through those it works (gives me all posts by tags when I select on a specific tag), but it also lists all the tags and not the ones specific to the post which I don't want. (tags are stored as strings) See in screen.
How do I set it up to where it only shows me the specific tag I'm hovering over and not all tags in the post?
Twig
{% for blog in pagination %}
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p><br><br>
{% endfor %}
Controller
public function indexAction($tag = null)
{
// Search function using code from Services/Search.php
$query = $this->get('search');
$results = $query->search();
$em = $this->getDoctrine()->getManager();
$blogs = $em->getRepository('AcmeDemoBundle:Blog')
->getBlogs();
// Get all tags
$tags = $em->getRepository('AcmeDemoBundle:Blog')
->getTags();
// Get all posts by tag
$postTags = $em->getRepository('AcmeDemoBundle:Blog')
->getPostsByTags($tag);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$blogs,
$this->get('request')->query->get('page', 1)/*page number*/,
5/*limit per page*/
);
return array(
'blogs' => $blogs,
'query' => $query,
'results' => $results,
'tags' => $tags,
'postTags' => $postTags,
'pagination' => $pagination,
);
}
public function tagAction($tag = null)
{
$em = $this->getDoctrine()->getManager();
$tags = $em->getRepository('AcmeDemoBundle:Blog')
->getPostsByTags($tag);
if (!$tags) {
throw $this->createNotFoundException('Unable to find blog posts');
}
return array(
'tags' => $tags,
);
}
Noticed the code you use:
{% for blog in pagination %}
Tags: {{ blog.tags }}
{% endfor %}
It is not a recommended way.
The correct way is you have to use a nested loop and the tag links should be presented something like:
tag1
tag2
...
It is never a good idea to store all the tags in just one field and separated with comma or whatever:
Book1 | tag1, tag2, tag3,...
But it is recommended to do like this:
Book1 | tag1
Book2 | tag2
...
Hope this helps.

Resources