Get subfield in flexible content layout - wordpress

Seems that I can't get the value out off the field “overview_title” on line 29. which is a subfield of the “overview_layout”.
JSON: https://pastebin.com/BLBb6cjx.
I can get the “overview_layout” field. but its subfields won't return anything. anyone?
What I'm trying to do is to get the values of those 3 custom fields. But I get nothing.
This is what i'm trying in Timber:
$post = new Timber\Post();
$context['post'] = $post;
$context['layout'] = get_field('cancer_type_layout');
$context['overview'] = array (
'title' => get_field('overview_title'),
'text' => get_field('overview_text'),
'image' => get_field('overview_picture')
);
Timber::render('templates/pages/template-cancer.twig', $context);```

When you work with Flexible Content fields in Timber, you mustn’t access subfields by more calls to get_field(). All data of the subfields is already there when you get the data with the name of the Flexible Content field.
$post = new Timber\Post();
$context['post'] = $post;
// Everything you need will be in block.
$context['block'] = $post->meta( 'cancer_type_layout' );
Timber::render( 'templates/pages/template-cancer.twig', $context );
In Twig, you can then access your data directly from the block variable:
{% if 'overview_layout' === block.acf_fc_layout %}
{{ block.overview_title }}
{{ block.overview_text }}
{{ block.overview_picture }}
{% endif %}

Related

How can i prevent a flexible content field to only render the last item?

I'm working on this flexible field where I have set it up so that the user in wp-admin can change the order of the content and also add new content modules. But if one module is used more than one time it's only the last one that will work. I believe its something I have made wrong in my loop. You can see the code below:
Basically, if the user adds two of the "overview_layout" the last one will work, the first one won't render. How would you solve this?
My ACF: https://pastebin.com/xAuqEtma
$context = Timber::get_context();
$post = new Timber\Post();
$context['post'] = $post;
$context['layout'] = get_field('cancer_type_layout');
if(have_rows('cancer_type_layout')):
while(have_rows('cancer_type_layout') ): the_row();
if(get_row_layout() == 'overview_layout'):
$context['overview'] = array (
'title' => get_sub_field('ct_overview_title'),
'text' => get_sub_field('ct_overview_text'),
'image' => get_sub_field('ct_overview_picture'),
'class' => 'bg-gray-100',
'gradient' => true
);
elseif(get_row_layout() == 'video_layout'):
$context['video'] = get_sub_field('ct_video_url');
elseif(get_row_layout() == 'statlist_layout'):
$context['statlist'] = array (
'title' => get_sub_field('ct_statlist_title'),
'text' => get_sub_field('ct_statlist_text'),
'list' => get_sub_field('ct_statlist_statlist'),
'button' => get_sub_field('ct_statlist_button'),
'buttontext' => get_sub_field('ct_statlist_button_text'),
'buttonlink' => get_sub_field('ct_statlist_button_link'),
'buttonfile' => get_sub_field('ct_statlist_button_file')
);
elseif(get_row_layout() == 'text_layout'):
$context['text'] = array (
'text' => get_sub_field('ct_text_text'),
'button' => get_sub_field('ct_text_button'),
'buttontext' => get_sub_field('ct_text_button_text'),
'buttlink' => get_sub_field('ct_text_button_link')
);
elseif(get_row_layout() == 'image_layout'):
$context['image'] = get_sub_field('ct_image_image');
elseif(get_row_layout() == 'qoutelist_layout'):
$context['quotelist'] = get_sub_field('ct_quotelist_quotes');
elseif(get_row_layout() == 'postlist_layout'):
$context['postlist'] = get_sub_field('ct_postlist_posts');
endif;
endwhile;
endif;
The main problem is that you overwrite variables in your context in each loop. When you set $context['overview] inside a loop, the content you set is not added to the existing content, but it overwrites what you already have. If you wanted that to work, you’d have to use an array and add your data there.
$context['overview'] = array();
if ( have_rows( 'cancer_type_layout' ) ):
while ( have_rows( 'cancer_type_layout' ) ): the_row();
if ( get_row_layout() == 'overview_layout' ):
$context['overview'][] = array(
'title' => get_sub_field( 'ct_overview_title' ),
'text' => get_sub_field( 'ct_overview_text' ),
'image' => get_sub_field( 'ct_overview_picture' ),
'class' => 'bg-gray-100',
'gradient' => true
);
By using $context['overview'][], you add the data to your existing array, instead overwriting it. However, if you do that for every single layout you have, you end up writing a lot of code. And you split the data into different variables. This will break the order of your layouts, because you only grouped the same Flexible Content layouts into on array.
I’d recommend to use a different approach, where you don’t have to prepare all the data in PHP and reduce the amount of code you have to write.
First, when you use get_field(), then you’re not making use of Timber’s functionality. You can use $post->meta() instead.
$context['layout'] = $post->meta( 'cancer_type_layout' );
Then, in your Twig file, you can loop over the layouts and include a file that displays the block:
{% if layout is not empty %}
{% for block in layout %}
{% include 'block/' ~ block.acf_fc_layout ~ '.twig' ignore missing %}
{% endfor %}
{% endif %}
For each of the layouts you have, you can now create a new Twig file with the name of the layout. In that file, all your data in the layout is available under the block variable.
block/overview_layout.twig
{{ block.title }}
{{ block.text }}
{# … #}
block/statlist_layout.twig
{{ block.title }}
{{ block.text }}
{{ block.list }}
{{ block.buttontext }}
{# … #}

Displaying Wordpress custom post type by first letter with Timber (twig)

I use Timber for Wordpress and I would like to create a dictionary system like that :
https://wordpress.stackexchange.com/questions/119163/displaying-custom-post-type-by-first-letter-through-custom-taxonomy
I wrote this :
lexique.php
$query = get_posts(array('post_type' => 'lexique','posts_per_page' => -1));
But I don't know how to transform this with Timber :
$by_letter = array();
while( $query->have_posts() ) { $query->the_post();
global $post;
$letter = substr($post->post_name, 0, 1);
if ( ! isset($by_letter[$letter]) ) $by_letter[$letter] = array();
$by_letter[$letter][] = $post;
}
wp_reset_postdata();
It’s a good idea to get all posts at once. By adding the orderby parameter, you can already put them into the correct order for the letter-sorting. I’d use title and not name for the sorting and to define the first letter, because name/post_name is a URL-safe string (used in permalinks) might be different from the actual title of the post.
You get the posts through Timber::get_posts(), so that you don’t have to rely on The Loop. You get an array with posts that you can work with, before you render it through a Twig file. This is much more direct than the method you linked to, because you don’t have to rely on additional functions and query resets.
lexique.php
$posts = Timber::get_posts( array(
'post_type' => 'lexique',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
) );
$posts_by_letter = array();
// Sort posts by letter
foreach ( $posts as $post ) {
$first_letter = substr( $post->post_title, 0, 1);
// Create array for letter if it doesn’t exist
if ( ! isset( $posts_by_letter[ $first_letter ] ) ) {
$posts_by_letter[ $first_letter ] = array();
}
$posts_by_letter[ $first_letter ][] = $post;
}
$context['posts_by_letter'] = $posts_by_letter;
Timber::render( [ 'lexique.twig' ], $context );
Display only letters for existing posts
lexique.twig
<dl>
{% for letter, posts in posts_by_letter %}
<dt>{{ letter }}</dt>
{% for post in posts %}
<dd>{{ post.title }}</dd>
{% endfor %}
{% endfor %}
</dl>
Because you have a nested array, you have to do two for-loops. The first loop goes through the letters (which are the keys of the outer array). The value assigned to a letter key is another array, containing all the posts starting with that letter. To display the post titles as links, you use the second for-loop.
Display all letters from A to Z
If you want to generate a list of all letters from A to Z and display existing posts, you can use range and check if posts exists for that letter in posts_by_letter.
Additionally you can use range to create a list of anchor links that lets a visitor jump to a specific letter.
lexique.twig
{# Anchor links to jump to letter #}
{% for letter in range('A', 'Z') %}
{{ letter }}
{% endfor %}
<dl>
{% for letter in range('A', 'Z') %}
<dt><a id="{{ letter }}">{{ letter }}</a></dt>
{% if posts_by_letter[letter] is defined %}
{% for post in posts_by_letter[letter] %}
<dd>{{ post.title }}</dd>
{% endfor %}
{% endif %}
{% endfor %}
</dl>

Twigpress: Can't pass variables to twig template

I am using Twigpress with Wordpress. According to the Twigpress doc, you can pass variables to the template with twigpress_render_twig_template($vals = array(), $template = false, $echo = true).
I'm trying to pass variables to the template with the following code but it doesn't work. What am I doing wrong?
single.php:
$vals = array( 'foo' => 'bar' );
twigpress_render_twig_template($vals);
single.twig:
{{ vals.foo }} # Does not print anything #
{{ foo }} # Same #
{{ dump(vals) }} # Prints 'null' #
Please enlighten a n00b! Thanks. :)
You have to enable debug in your twig setting.
This is how I do it for my Twig initialization. I put this in a separate file call twig-init.php and just require_once where i need to use twig.
$loader = new Twig_Loader_Filesystem('/blah/twig/templates');
$settings = array(
'cache' => '/blah/twig/compiled',
'debug' => true
);
$twig = new Twig_Environment($loader, $settings);
if ($settings['debug'])
$twig->addExtension(new Twig_Extension_Debug());
When you dump it, you can just do {{ dump() }} to dump everything instead.
In addition, you may need to access your array value via the _context. Try to {{ dump(_context['foo']) }}
If you really want to access the variable via vals, you will have to do the following to your array:
$blah = array('vals' => array('foo' => 'bar'));
Then {{ vals.foo }} will work.
See: http://twig.sensiolabs.org/doc/functions/dump.html

Symfony QueryBuilder count request returns "array"

I'm trying to make a count request on an id field.
I have the following code in my controller:
$qb = $em->createQueryBuilder();
$qb->select('count(c.id)')
->from('MyBundle:Category', 'c')
->where('c.author = :aut')
->setParameter('aut', $author);
$result = $qb->getQuery()->getResult();
return $this->render('MyCategoryBundle:Category:categories.html.twig', array(
'categories' => $categories,
'result' => $result
));
And in my view.html.twig :
{% for category in categories %}
<li>
{{ category.title }}
{% for total in result %}
{{ total }}
{% endfor %}
</li>
{% endfor %}
It returns the category title and the string "Array" just besides. What am I doing wrong ?
The getResult() returns an array (collection) of objects. You have to use the getSingleScalarResult() function in order to get the result as a simple number.
Anyway, what is the goal of your code? If you want to display the number of categories for each author, your code may not work since you display a list of categories but you only count the categories from one author.
Try this:
$qb = $em->createQueryBuilder();
$qb->select('count(c.id) AS cnt')
->from('MyBundle:Category', 'c')
->where('c.author = :aut')
->setParameter('aut', $author);
$result = $qb->getQuery()->getResult();
and then in twig {{result.cnt}}

Symfony2 & Twig : display all fields and keys

I have a problem with Symfony2 and Twig: I don't know how to display all fields of my entity which is loaded dynamically. Here is my code (displays nothing!!)
Controller :
public function detailAction($id)
{
$em = $this->container->get('doctrine')->getEntityManager();
$node = 'testEntity'
$Attributes = $em->getRepository('TestBetaBundle:'.$node)->findOneById($id);
return $this->container->get('templating')->renderResponse('TestBetaBundle:test:detail.html.twig',
array(
'attributes' => $Attributes
));
}
detail.html.twig :
{% for key in attributes %}
<p>{{ value }} : {{ key }}</p>
{% endfor %}
Don't settle for just the public properties! Get the private/protected as well!
public function detailAction($id){
$em = $this->container->get('doctrine')->getEntityManager();
$node = 'testEntity'
$Attributes = $em->getRepository('TestBetaBundle:'.$node)->findOneById($id);
// Must be a (FQCN) Fully Qualified ClassName !!!
$MetaData = $em->getClassMetadata('Test\Beta\Bundle\Entity\'. $node);
$fields = array();
foreach ($MetaData->fieldNames as $value) {
$fields[$value] = $Attributes->{'get'.ucfirst($value)}();
}
return $this->container
->get('templating')
->renderResponse('TestBetaBundle:test:detail.html.twig',
array(
'attributes' => $fields
));
}
OK. What you are trying to do cannot be done with a Twig for loop over your attributes object. Let me try to explain:
The Twig for loop iterates over an ARRAY of objects, running the inside of the loop for each of the objects in the array. In your case, $attributes is NOT an array, it is an OBJECT which you retrived with your findOneById call. So the for loop finds that this is not an array and does not run the inside of the loop, not even once, that is why you get no output.
The solution proposed by #thecatontheflat does not work either, as it is just the same iteration over an array, only that you have access to both the keys and values of the array, but since $attributes is not an array, nothing is accomplished.
What you need to do is pass the template an array with the properties of the $Attributes object. You can use the php get_object_vars() function for this. Do something like:
$properties = get_object_vars ($Attributes);
return $this->container->get('templating')->renderResponse('TestBetaBundle:test:detail.html.twig',
array(
'attributes' => $Attributes
'properties' => $properties
));
And in the Twig template:
{% for key, value in properties %}
<p>{{ value }} : {{ key }}</p>
{% endfor %}
Take into account that this will only show the public properties of your object.
For Symfony3
$em = $this->getDoctrine()->getEntityManager();
$MetaData = $em->getClassMetadata('TestBetaBundle:Node');
$fields = $MetaData->getFieldNames();
return $this->render('test/detail.html.twig', array('fields'=>fields));
You should change it to
{% for key, value in attributes %}
<p>{{ value }} : {{ key }}</p>
{% endfor %}

Resources