I'm creating a website using CakePHP. On my main page I want to display the six latest videos and the six most popular videos. I've made an element named 'itemList' that I pass a type parameter and an array with items.
I use the following code for the latest videos:
$this->element('itemList', array('type' => "video", 'items' => $latestVideos));
echo $this->fetch('itemList');
And the following code for the popular videos:
$this->element('itemList', array('type' => "video", 'items' => $popularVideos));
echo $this->fetch('itemList');
The latest videos get displayed just the way they should, but the popular videos first show the latest videos (for the second time) and the popular videos afterwards.
Does anyone know how I can "clear" or "unset" the first itemList element, to start off with an empty one when it gets used for the second time?
<?php
/* The following parameters are set when this element is called upon:
* $type: article, video
* $items: the list with items to be displayed
*/
$this->start('itemList');
echo $this->Html->script('equalColumns');
$itemCount = 0;
$mainType = $type;
$listType = null;
// If the article list is called, use wider columns css (.articleList)
if ($type == "article") {
$listType = " articleList";
}
?>
<ul class="otherArticles<?php print($listType); ?>">
<?php
foreach ($items as $item) {
$duration = null;
$coverImage = null;
$coverImageOffset = array("x" => 0, "y" => 0);
/* If a list of tags is submitted, check for every item if the item is an article or a
* video
*/
if ($mainType == "tag") {
if ($item["TagRelationship"]["article_id"] != null) {
$newItem["Article"]["id"] = $item["TagRelationship"]["article_id"];
$newItem["Article"]["slug"] = $item["Article"]["slug"];
$newItem["Article"]["title"] = $item["Article"]["title"];
$newItem["Article"]["created"] = $item["Article"]["created"];
$newItem["User"] = $item["Article"]["User"];
$newItem["Album"]["Photo"] = $item["Article"]["Album"]["Photo"];
$item = $newItem;
$type = "article";
} elseif ($item["TagRelationship"]["video_id"] != null) {
$type = "video";
}
}
// If a list with videos is supplied, format the duration
if ($type == "video") {
// Set the coverImage
$coverImage = $this->CoverImage->getYouTubeCover($item["Video"]["youtubeId"]);
// If a video lasts shorter than an hour, only show minutes/seconds
if ($item[ucfirst($type)]["duration"] < 3600) {
$duration = gmdate("i:s", $item[ucfirst($type)]["duration"]);
}
// Otherwise show hours as well
else {
$duration = gmdate("H:i:s", $item[ucfirst($type)]["duration"]);
}
} elseif ($type == "article") {
$coverImage = $this->CoverImage->getArticleCover($item["Article"]["id"], $item["Album"]["Photo"]);
$coverImageOffset = $this->CoverImage->getArticleCoverOffset($item["Article"]["id"], $item["Album"]["Photo"]);
}
?>
<li>
<a href="/<?php print($type); ?>s/<?php print($item[ucfirst($type)]["id"]); ?>/<?php print($item[ucfirst($type)]["slug"]); ?>">
<p class="addedDate">Added:
<?php
print($this->Time->timeAgoInWords($item[ucfirst($type)]["created"], array(
'accuracy' => array('minute' => 'minute', 'hour' => 'hour', 'week' => 'week', 'day' => 'day', 'month' => 'month', 'year' => 'year'),
'end' => 'never')));
if ($type == "article") {
?>
by <?php print($item["User"]["username"]); ?>
<?php
}
?>
</p>
<ul class="itemDetails">
<li class="thumb" style="background-image: url(<?php print($coverImage); ?>); background-position: <?php print($coverImageOffset["x"]); ?>% <?php print($coverImageOffset["y"]); ?>%">
<?php
if ($mainType == "tag") {
?>
<p class="label"><?php print(ucfirst($type)); ?></p>
<?php
}
if ($type == "video") {
?>
<p class="duration"><?php print($duration); ?></p>
<?php
}
?>
</li>
<li>
<p><?php print($item[ucfirst($type)]["title"]); ?></p>
</li>
</ul>
</a>
</li>
<?php
}
?>
</ul>
<?php
$this->end();
?>
More than likely you are not unsetting the variables you set in the last element, they are remembered throughout the view. Try unsetting items after the first call -
$this->element('itemList', array('type' => "video", 'items' => $latestVideos));
echo $this->fetch('itemList');
unset($items);
Like the comment said though if this doesn't work we would need to see your element itself to diagnose the problem. If you go into your element itself at the end of everything being processed, unset the array housing all the items you are displaying. This should do the trick.
After some more research I came across the following in the CakePHP Cookbook:
// Clear the previous content from the sidebar block.
$this->assign('sidebar', '');
Sure enough this did the trick.
Thanks for your help though guys!
Related
I have made a custom taxonomy archive page called taxonomy-country.php. The file runs perfectly and loops through the current country and displays the posts within it.
Above this loop on the same templeate I want to display a map of all the post locations using Advanced Custom Fields. I've used the code before with no problems but not in an archive file, however when used at the top of the template the map and markers show fine but the standard archive loop no longer displays.
What is wrong with the wpquery that it kills the loop after it? Or is there another reason I can't run he query above the normal loop on an archive page?
<?php
// WP_Query arguments
$args = array (
'post_type' => 'home',
'order' => 'ASC',
'orderby' => 'title',
'posts_per_page' => '-1',
);
// The Query
$query = new WP_Query( $args );
// The Loop
if ( $query->have_posts() ) { ?>
<div class="acf-map">
<?php while ( $query->have_posts() ) {
$query->the_post(); ?>
<?php
$location = get_field('location');
if( !empty($location) ):
?>
<div class="acf-map">
<div class="marker" data-lat="<?php echo $location['lat']; ?>" data-lng="<?php echo $location['lng']; ?>">
<h2 class="name"><?php the_title(); ?></h2>
<strong class="number"><?php echo do_shortcode('[mrp_rating_result rating_form_id="2"]'); ?></strong>
</div>
</div>
<?php endif; ?>
<?php }
} else {
// no posts found
}
// Restore original Post Data
wp_reset_query() ?>
UPDATE:
Just found out this should be shorter alternative:
wp_reset_postdata()
ORIGIONAL:
This line killed it:
// Restore original Post Data
wp_reset_query() ?
Reason: You did not use main $wp_query, hence $query->the_post() did not interfere with the current index of $wp_query. Resetting it will cause the main loop to restart.
Reference: https://codex.wordpress.org/Function_Reference/wp_reset_query
A safer way is to:
Before the category loop:
global $post;
$temp_post = $post;
After the category loop:
$post = $temp_post;
Just 3 lines and it should work.
Cheers!
As per above answer you have to store the $post in in temporary variable and restore it before wp_reset_query()
example function code look like below
function cd_meta_box_cb_hotel( $post )
{
global $post;
$temp_post = $post;
$selected=get_post_meta($post->ID, 'hotel_location_id',true);
$mquery = new WP_Query(array(
'post_type' => 'custom posttype',
'post_status' => 'publish',
'posts_per_page' => -1,
));
while ($mquery->have_posts()) {
$mquery->the_post();
// doing what you need
}
**$post = $temp_post;
wp_reset_query();**
}
Currently using ACF Repeater for WP to show some posts within a category but if I add the same repeater I want it to keep a log of what post ids have been used so it can exclude them from the new loop.
The only problem is my current code works fine for the first loop and the second but adding anymore than two just resets back to the first set of posts. Dumping the array looks like it is not adding to the array just overwriting it.
First array looks like this
array(3) { [0]=> int(28890) [1]=> int(28790) [2]=> int(28785) }
Second array
array(3) { [0]=> int(28749) [1]=> int(1) [2]=> int(28484) }
Third
array(3) { [0]=> int(28890) [1]=> int(28790) [2]=> int(28785) }
Here is my code
<?php
$cat = get_sub_field('category_name');
$args = array(
'posts_per_page' => 3,
'category_name' => $cat,
'post__not_in' => $ids
);
query_posts( $args );
$ids = array();
?>
<div class="hub-cont">
<?php while (have_posts()) : the_post(); ?>
<?php array_push($ids,get_the_ID()); /*$ids[] = get_the_ID();*/?>
<div class="blockitem2 small-12 medium-4 large-4">
<?php
// Fetch all posts relating to a certain tag then display 4 of them
//Get the Thumbnail URL
$src = wp_get_attachment_image_src( get_post_thumbnail_id($post->ID), array( 720,405 ), false, '' );
?>
<div id="promolink"></div><div class="blockimage" style="background-image: url('<?php echo $src[0]; ?>'); background-repeat: no-repeat; background-size: cover;">
<div class="cats"><?php echo the_category(' '); ?></div>
</div>
<div class="meta">
<a class="gdbnewslink dark" href="<?php echo get_permalink();?>" ><?php the_title();?> </a>
</div>
<div class="clear"></div>
<div id="newsintro"><?php $text = $post->post_content; $trimmed = wp_trim_words( $text, 50, null ); echo $trimmed; ?></div>
</div>
<?php endwhile; ?>
<?php wp_reset_query(); ?>
<?php var_dump($ids); ?>
</div>
Arrays are still pretty new to me so your guidance will be greatly appreciated!
Here is the solution using information from this link. https://www.binarymoon.co.uk/2010/03/5-wordpress-queryposts-tips/
Add this to your functions file.
$bmIgnorePosts = array();
/**
* add a post id to the ignore list for future query_posts
*/
function bm_ignorePost ($id) {
if (!is_page()) {
global $bmIgnorePosts;
$bmIgnorePosts[] = $id;
}
}
/**
* reset the ignore list
*/
function bm_ignorePostReset () {
global $bmIgnorePosts;
$bmIgnorePosts = array();
}
/**
* remove the posts from query_posts
*/
function bm_postStrip ($where) {
global $bmIgnorePosts, $wpdb;
if (count($bmIgnorePosts) > 0) {
$where .= ' AND ' . $wpdb->posts . '.ID NOT IN(' . implode (',', $bmIgnorePosts) . ') ';
}
return $where;
}
add_filter ('posts_where', 'bm_postStrip');
Then to use this you would do your loop as normal, and call ‘bm_ignorePost($post->ID);’ for each post you want to ignore. The following example uses the same query twice, but will display totally different posts on each output.
<?php
// set the query
$query = 'posts_per_page=10';
// loop 1 - display most recent 10 posts
$queryObject = new WP_Query($query);
if ($queryObject->have_posts()) {
while ($queryObject->have_posts()) {
bm_ignorePost($queryPost->post->ID);
$queryObject->the_post();
the_title();
the_content();
}
}
// loop 2 - same query, get the next 10 posts
$queryObject = new WP_Query($query);
if ($queryObject->have_posts()) {
while ($queryObject->have_posts()) {
bm_ignorePost($queryPost->post->ID);
$queryObject->the_post();
the_title();
the_content();
}
}
?>
Im pretty new in WP so pls forgive me my ignorance im trying to learn.
So I have two Wp_Query (search by typeing in searchbar and search by taxonomy checkboxes) and I dont know how to mix them... If one works second dont and its a cricle.
<?php
global $search_ingr;
if(isset($search_ingr)) {
global $loop;
} else {
$query_params = getQueryParams();
if(isset($query_params['search'])) {
$query_params['post_title_like'] = $query_params['search'];
unset($query_params['search']);
}
$loop = new WP_Query(array(
'numberposts' => 60,
'posts_per_page' => 60,
'orderby' => 'title',
'order' => 'ASC',
'post_type' => 'products',
'post_status' => 'publish'
));
}
?>
<?php if(isset($search)): ?>
<div class="search-matches"><h4>Wyniki wyszukiwania:</h4><hr></div>
<?php endif; ?>
<?php if ($loop->have_posts()) :?>
<?php while($loop->have_posts()) : $loop->the_post (); ?>
<span class="tooltip tooltip-effect-4">
<span class="tooltip-item">
<div class="product">
<a href="<?php the_permalink(); ?>">
<div class="product-thumbnail"><span class="helper"></span><?php the_post_thumbnail(); ?></div>
<div class="product-name"><?php the_title(); ?></div>
</a>
</div>
</span>
<a href="<?php the_permalink(); ?>">
<span class="tooltip-content clearfix">
<?php the_post_thumbnail(); ?>
<span class="tooltip-text"><?php the_title(); ?></span>
</span>
</a>
</span>
<?php endwhile; ?>
<?php endif; ?>
Pretty long peace of code, Im pasting it with loop as well and above that I have search box and taxonomy list.
Additionally when I remove this:
// $loop = new WP_Query(array(
// 'numberposts' => 60,
// 'posts_per_page' => 60,
// 'orderby' => 'title',
// 'order' => 'ASC',
// 'post_type' => 'products',
// 'post_status' => 'publish'
// ));
Both searches works great(but I cant control post order and number of posts).
Any help on that?
The proper way to modify the wordpress loop would be like this.
Try this instead of the first block of PHP on top:
<?php
$search = get_query_var('s');
$loop = new WP_Query(array(
'numberposts' => 60,
'posts_per_page' => 60,
'orderby' => 'title',
'order' => 'ASC',
'post_type' => 'products',
'post_status' => 'publish',
's' => $search
));
?>
The potential problem is that you are using functions and parameters which aren't native to wordpress - getQueryParams() isn't a wordpress function and the WP_Query function doesn't accept a parameter with name 'post_title_like'. If this code works then you might be using some plugin for the search functionality in which case you would have to override the plugin behavior, not the default wordpress query.
Thanks a lot! Really appreciate youre help but it didnt resolve my problem.
I will try another way
This piece of code work perfectly for both searches but I need to change number of posts above limit of 10(thats why I started to messing with this code):
<?php
global $search_ingr;
if(isset($search_ingr)) {
global $loop;
} else {
$query_params = getQueryParams();
if(isset($query_params['search'])) {
$query_params['post_title_like'] = $query_params['search'];
unset($query_params['search']);
}
$loop = new WP_Query($query_params);
}
?>
And the getQueryParams() function:
function getQueryParams(){
global $query_string;
$parts = explode('&', $query_string);
$return = array();
foreach($parts as $part){
$tmp = explode('=', $part);
$return[$tmp[0]] = trim(urldecode($tmp[1]));
}
return $return;
}
I have to use a form once in footer and in individual page i.e. index.ctp.
For that I have a database table named contactforms.
I have created a model Contactform.php
<?php
App::uses('AppModel', 'Model');
/**
* Contactform Model
*
*/
class Contactform extends AppModel {
/**
* Validation rules
*
* #var array
*/
var $useTable = false;
public $validate = array(
'firstname' => array(
'notempty' => array(
'rule' => array('notempty')
)
),
'contactno' => array(
'notempty' => array(
'rule' => array('notempty')
)
),
'email' => array(
'notempty' => array(
'rule' => array('notempty')
)
)
);
}
?>
I have a controller from where I am trying to send an email
<?php
App::uses('AppController', 'Controller');
App::uses('CakeEmail', 'Network/Email');
class ContactformsController extends AppController {
public function index() {
$this->Contactform->recursive = 0;
$this->set('contactforms', $this->paginate());
}
public function contact() {
$email = new CakeEmail();
if(isset($this->params['requested']) && $this->params['requested']==true)
{
if ($this->request->is('post'))
{
$this->Contactform->set($this->request->data);
if($this->Contactform->save($this->request->data))
{
$name=$this->request->data['Contactform']['firstname'];
$lastname=$this->request->data['Contactform']['lastname'];
$contact=$this->request->data['Contactform']['contactno'];
$mail= $this->request->data['Contactform']['email'];
$email->from(array($mail => $name));
$email->to('abc#gmail.com');
$message= $this->request->data['Contactform']['message'];
$email->subject('Wombats contact form information');
if($email->send($message))
{
$this->Session->setFlash('Quote Processed..Thank You For Visiting Our Website!!!');
$this->redirect($this->referer());
}
}
}
}
}
}
?>
And then I created an element which I used called in footer and then in index file.
contact.ctp looks like
<?php echo $this->Html->css('contactfooter.css');?>
<?php $contactforms = $this->requestAction('Contactforms/contact') ?>
<div class="frm">
<?php echo $this->Form->create('Contactform'); ?>
<div class="firstrow">
<div class="first">
<?php echo $this->Form->input('firstname',array('label'=>false,'placeholder'=>'firstname','div'=>'firstname','style'=>'width:130px; height:20px;' ));?>
<?php // echo $this->Form->input('firstname',array('label'=>false,'placeholder'=>'firstname','style'=>'width:130px; height:20px; float:left; margin-right:5px;','error'=>array('attributes'=>array('wrap'=>'div','class'=>'errorfirst'))));?>
</div>
<div class="second">
<?php echo $this->Form->input('lastname',array('label'=>false,'placeholder'=>'lastname','div'=>'lastname','style'=>'width:140px; height:20px; '));?>
</div>
</div>
<!--<div class="secondrow">-->
<?php echo $this->Form->input('contactno',array('label'=>false,'placeholder'=>'contactno','div'=>'contactno','style'=>'width:270px; height:20px; margin-bottom:10px;'));?>
<!--</div>-->
<?php echo $this->Form->input('email',array('label'=>false,'placeholder'=>'email','div'=>'email','style'=>'width:270px; height:20px; '));?>
<?php echo $this->Form->input('message',array('label'=>false,'placeholder'=>'message','div'=>'message','style'=>'width:270px; height:25px;margin-top:10px; '));?>
</div>
<!--<br>-->
<div class="sub">
<?php echo $this->Form->end('SUBMIT'); ?>
</div>
When I click submit of one form other form as well validates.
I tried a lot but don't know how to fix Can anyone please help.
EDIT:-
#Nunser I am very confused with these names sorry for that. I have changed my code according to what u told but still its validating one form only.
I have a doubt, according to you I should change in view and elements too but I just have elements.Please can u help I am posting my edited code
I called element from index page as
<?php echo $this->element('Contactform/contact',array('source'=>'index')); ?>\
and from default page as
<?php echo $this->element('Contactform/contact'); ?>
my controller action is
public function contact() {
$email = new CakeEmail();
if(isset($this->params['requested']) && $this->params['requested']==true){
if ($this->request->is('post'))
{
$index = 'Contactform';
if (isset($this->request->data['Contactformindex']))
$index = 'Contactformindex';
$this->Contactform->set($this->request->data[$index]);
if($this->Contactform->save($this->request->data[$index]))
{
$name=$this->request->data[$index]['firstname'];
$lastname=$this->request->data[$index]['lastname'];
$contact=$this->request->data[$index]['contactno'];
$mail= $this->request->data[$index]['email'];
$email->from(array($mail => $name));
$email->to('skyhi13#gmail.com');
$message= $this->request->data[$index]['message'];
$email->subject('Wombats contact form information');
//$email->send($message);
if($email->send($message))
{
$this->Session->setFlash('Quote Processed..Thank You For Visiting Our Website!!!');
//$this->render('/view/elements/quotes/quoteform.ctp');
// $this->autoRender=FALSE;
$this->redirect($this->referer());
}
}
else {
$this->set('formName',$index);
}
}
}
}
In the elements ctp file I changed as
<?php if (!empty($this->validationErrors['Contactform'])) {
$this->validationErrors[$formName] = $this->validationErrors['Contactform'];
}?>
<div class="frm">
<?php
if(isset($source)&& $source == 'index')
echo $this->Form->create('Contactformindex');
else
echo $this->Form->create('Contactform');
?>
<div class="firstrow">
<div class="first">
<?php echo $this->Form->input('firstname',array('label'=>false,'placeholder'=>'firstname','div'=>'firstname','style'=>'width:130px; height:20px;' ));?>
<?php // echo $this->Form->input('firstname',array('label'=>false,'placeholder'=>'firstname','style'=>'width:130px; height:20px; float:left; margin-right:5px;','error'=>array('attributes'=>array('wrap'=>'div','class'=>'errorfirst'))));?>
</div>
<div class="second">
<?php echo $this->Form->input('lastname',array('label'=>false,'placeholder'=>'lastname','div'=>'lastname','style'=>'width:140px; height:20px; '));?>
</div>
</div>
<!--<div class="secondrow">-->
<?php echo $this->Form->input('contactno',array('label'=>false,'placeholder'=>'contactno','div'=>'contactno','style'=>'width:270px; height:20px; margin-bottom:10px;'));?>
<!--</div>-->
<?php echo $this->Form->input('email',array('label'=>false,'placeholder'=>'email','div'=>'email','style'=>'width:270px; height:20px; '));?>
<?php echo $this->Form->input('message',array('label'=>false,'placeholder'=>'message','div'=>'message','style'=>'width:270px; height:25px;margin-top:10px; '));?>
</div>
<!--<br>-->
<div class="sub">
<?php echo $this->Form->end('SUBMIT'); ?>
</div>
Using this code still it validated one form only and form is that which I have called without source as index and when clicked on index submit button it validates the other form. I am not sure as do I have to use the same Fromindex name as specified by you, does that matter. I am not able to find as where I am going wrong.Please help and Thanks in advance.
First, why are you doing this
<?php $contactforms = $this->requestAction('Contactforms/contact') ?>
in your element? I don't see the use of that requestAction except slowing down the processing...
Ok, but your problem...
Since you're calling an element, there's no easy way to avoid the validation of both forms. Usually, when there are two forms corresponding to the same model, the way to not validate both is to change the "name" of the form like this
<!--in the first form-->
<?php echo $this->Form->create('Contactform1'); ?>
<!--in the second form-->
<?php echo $this->Form->create('Contactform2'); ?>
But, since you are creating that form with an element, there's no easy way of changing the name of the form in one place and not in the other...
So this is what you should do (there may be other ways of doing what you want, but this is the one I can think of). When you call the contact.ctp element, pass a variable to it
echo $this->element('contact', array(
"source" => "index"
));
With that, you know you're calling the element from the index.ctp, for example. Then, in the element, change the form declaration depending on the variable
//the beginning of your element
<?php
if (isset($source) && $source == 'index')
echo $this->Form->create('FromIndex');
else
echo $this->Form->create('FromContact');
?>
You'll also need to modify your action, to read the data in FromIndex or in FromContact, depending on where it came from
public function contact() {
$email = new CakeEmail();
if(isset($this->params['requested']) && $this->params['requested']==true) {
if ($this->request->is('post')) {
//change the index of the array depending on where it came form
$index = 'FromContact';
if (isset($this->request->data['FromIndex']))
$index = 'FromIndex';
$this->Contactform->set($this->request->data[$index]);
if($this->Contactform->save($this->request->data[$index]))
{
$name=$this->request->data[$index]['firstname'];
$lastname=$this->request->data[$index]['lastname'];
$contact=$this->request->data[$index]['contactno'];
$mail= $this->request->data[$index]['email'];
$email->from(array($mail => $name));
$email->to('abc#gmail.com');
$message= $this->request->data[$index]['message'];
$email->subject('Wombats contact form information');
if($email->send($message))
{
$this->Session->setFlash('Quote Processed..Thank You For Visiting Our Website!!!');
$this->redirect($this->referer());
}
}
}
}
}
}
When you save or set something (unless is with saveAssociated, saveAll or those kind of functions, when you're saving more than one model), there's no need to specify the model in the array to be saved.
$saveMe = array('User'=>array('name'=>'John'));
$saveMeToo = array('name'=>'John');
$this->User->save($saveMe); //will work
$this->User->save($saveMeToo); //will work too
That's why the change of $this->request->data indexes will work either way. But the validation will be for the specific index (FromContact or FromIndex).
Note: I haven't tested the code, so be sure to check for missing parenthesis, unclosed ' and those kind of things.
EDiT
#winnie pointed out that the validation only happened for one form, and that's because I overlooked something. The validation errors get displayed in the view if there's something set in this variable $this->validationErrors['ModelName'] in the view. And that's what I missed. So, re-change the action to pass the $index of the model to the view, so the view knows which form called the action, like this
public function contact() {
$email = new CakeEmail();
if(isset($this->params['requested']) && $this->params['requested']==true) {
if ($this->request->is('post')) {
//change the index of the array depending on where it came form
$index = 'FromContact';
if (isset($this->request->data['FromIndex']))
$index = 'FromIndex';
$this->Contactform->set($this->request->data[$index]);
if($this->Contactform->save($this->request->data[$index]))
{
//this if is exactly the same as the other one
} else {
//now, if there's no save, there's some validation errors
//tell the view which form called this save
$this->set('formName', $index);
}
}
}
Now in the view you need to copy the errors you get in the model, to the "fake model name" we gave the form. In the first line of your view do this (and in the element too)
if (!empty($this->validationErrors['Contactform'])) {
$this->validationErrors[$formName] = $this->validationErrors['Contactform'];
}
We have two models. Ebooks HABTM Tags, where tags follows the tree behavior.
For each tag we need two numbers. First, the number of ebooks associated to the tag, and secondly the number of ebooks associated to the tag + the number of associated ebooks for each descendant.
How can we get the tags with these numbers in an array in tree format?
Thank you very much for any help.
Update: There is a datetime parameter Ebook.published which defines when the book is to be counted or not. All the ebooks that have codeEbook.published < NOW() should be counted.
Cake has no basic support for this. You will need to do the calculations on the fly or create your own counter cache with custom code to update. This is messy.
I'd suggest overriding the beforeSave() and afterSave() function in your Ebooks controller. If updating, grab the current existing set of tags associated with the Ebook in your beforeSave(). In the afterSave() grab the new set of tags and merge it with the previous set. If there are any changes, iterate through all the tags and call $this->Tag->getPath($id) to get a list of all the ancestors. You'll now have all the tags that were affected by the save. You can now iterate through them and update the counts.
Actually, I found a simpler solution since I already have build the function that returns the tags tree. I used a query for each tag to get the actual count of that moment. Here is what I 've build. Feel free to use it according your needs.
In Tag model
// Returns the number of published ebooks of selected tag
function count_direct_published($tag_id = 0){
$temp = $this->query('SELECT count(*) as count FROM ebooks_tags LEFT JOIN ebooks ON ebooks_tags.ebook_id = ebooks.id WHERE ebooks.published < NOW() AND ebooks_tags.tag_id = '.$tag_id);
return $temp[0][0]['count'];
}
// Returns an array in tree format with $id tag and all his children
// $id = 0 start from the top (parent_id = null), or, from $id = the top's tag id
// $limit = boolean (default false)
// $level = Is the limit of depth applied only if $limit = true
// $ext = true Means this is the first time the function is called
// You can run tree_builder(), returns all the tree,
function tree_builder($id = 0, $limit = false, $level = 1, $ext = 1){
if($ext == 1){
$ext = 0;
$undo = true;
}else{
$undo = false;
}
$this->recursive=-1;
$this->contain('EbooksTag');
$var = array();
$count_all = 0;
// If limit = too big , exit
if($limit !== false && $level > $limit){
return '';
}
// Or else,
// If $id=0, find all the children
if($id == 0){
$tags = $this->find('all',array('conditions'=>array( 'Tag.parent_id IS NULL'), 'order'=>array('Tag.gre')));
// If $id!=0 && runs internally
}elseif($id != 0 && !$undo ){
$tags = $this->find('all',array('conditions'=>array( 'Tag.parent_id'=>$id ), 'order'=>array('Tag.gre')));
}
// If $id!=0 && is called from outside
elseif($id != 0 && $undo){
$tags = $this->find('all',array('conditions'=>array( 'Tag.id'=>$id )));
}
foreach($tags as $key => $tag){
$var[] = $tag;
$next = $this->tree_builder($tag['Tag']['id'], $limit, $level+1, $ext);
end($var); // move the internal pointer to the end of the array
$last_key = key($var); // fetches the key of the element pointed to by the internal pointer
$var[$last_key]['children'] = $next['var'];
$counter_direct = $this->count_direct_published($id);
$var[$last_key]['Tag']['count_all'] = $next['count_all']+$counter_direct;
$count_all += $var[$last_key]['Tag']['count_all'];
}
if( $undo )
{
return $var;
}else{
return array('count_all'=> $count_all, 'var' => $var);
}
}
In tags_controller.php
$this->set('tags', $this->Tag->tree_builder());
In the view
<?php foreach($tags as $tag){?>
<?php // Ο Γονέας σε dropdown box ?>
<div class="main-categ">
<?php echo $tag['Tag']['gre']; ?>
<?php echo $html->image('layout/arrows.png', array('alt'=> "Expand")); ?>
</div>
<div class="collapse">
<?php // Τα στοιχεία του γονέα ?>
<div class="tag-1">
<span class="tag-1">
<?php // Αν ?>
<?php if($tag['Tag']['count_direct']>0){
// Display link
echo $html->link($tag['Tag']['gre'],array('action'=>'view',$tag['Tag']['id']));
echo ' ('.$tag['Tag']['count_direct'].')';
}else{
// Display text
echo $tag['Tag']['gre'];
} ?>
</span>
<?php echo $html->link( 'view all' ,array('action'=>'view_all',$tag['Tag']['id'])); ?>
(<?php echo $tag['Tag']['count_all']; ?>)
</div>
<?php // Για κάθε πρώτο παιδί ?>
<?php foreach($tag['children'] as $tag_1){ ?>
<div>
<span class="tag-2">
<?php if($tag_1['Tag']['count_direct']>0){
// Display link
echo $html->link($tag_1['Tag']['gre'],array('action'=>'view',$tag_1['Tag']['id']));
echo ' ('.$tag_1['Tag']['count_direct'].')';
}else{
// Display text
echo $tag_1['Tag']['gre'];
} ?>
</span>
<?php echo $html->link( 'view all' ,array('action'=>'view_all',$tag_1['Tag']['id'])); ?>
(<?php echo $tag_1['Tag']['count_all']; ?>)
<?php // Τα δεύτερα παιδιά ?>
<?php $i=0; ?>
<?php foreach($tag_1['children'] as $tag_2){ ?>
<?php if($i==0){ echo '<ul class="split">'; $i++; } ?>
<li>
<?php if($tag_2['Tag']['count_direct']>0){
// Display link
echo $html->link($tag_2['Tag']['gre'],array('action'=>'view',$tag_2['Tag']['id']));
echo ' ('.$tag_2['Tag']['count_direct'].')';
}else{
// Display text
echo $tag_2['Tag']['gre'];
} ?>
</li>
<?php } ?>
<?php if($i==1) echo '</ul>'; ?>
<div class="clear"></div>
</div>
<?php } ?>
</div>
Perhaps its not the best solution but it works. Hope that helps