I'm just trying to get my head around Kohana, so if I'm going about this the wrong way, let me know!
I want to group results in my output. The way I did this in vanilla-PHP / PDO was to prepare a query, and then execute it within the output results. I can't get there in Kohana (3.2.2), though.
In this case, on my 'terms' page, I want to group 'terms' by 'type', with each group of terms separated by a 'type' header.
My 'terms' controller is (simplified):
class Controller_Terms extends Controller {
public function action_index()
{
$view = View::factory('terms');
// types of term - for grouping terms together
$sql = 'SELECT DISTINCT Type
FROM Term';
$view->types = DB::query(Database::SELECT, $sql)->as_object()->execute();
// list of terms - executed separately for each type
$sql = 'SELECT TermId, Term
FROM Term
WHERE Type = :type';
$view->terms = DB::query(Database::SELECT, $sql)->as_object();
$this->response->body($view);
}
}
And the 'terms' view includes (simplified):
<? foreach ($types as $type): ?>
<h2><?= $type->Type ?></h2>
<? $params = array(':type' => $type->Type);
$terms->parameters($params)->execute();
foreach ($terms as $term): ?>
<?= $term->Term ?>
<? endforeach // term ?>
<? endforeach // type ?>
I get the 'type' headers ok, but I don't get any terms within each type.
Any suggestions appreciated!
execute() returns a special object (Database_Result), so you need something like this:
$items = $terms->parameters($params)->execute();
foreach ($items as $term): ?>
...
Related
In this issue I have 2 tables involved: jobs and areas.
I'm trying to create a search element which will be available on all views and contain a textfield for filtering rows by jobs title or jobs description and a droplist for filtering rows by areas and I want them to all work together.
So I created a search function on JobsController:
public function search()
{
if($this->request->data('area_select') != 'select area') {
$jobs = $this->Jobs
->find('all')
->contain(['Types', 'Categories' => function($q) {
return $q
->where([
'Jobs.title OR Jobs.description LIKE' =>
"%" . $this->request->data('keywords') . "%"
])
->where([
'Jobs.area LIKE' =>
"%" . $this->request->data('area_select') ."%"
]);
}]
);
} else {
$jobs = $this->Jobs
->find('all')
->contain(['Categories' => function($q) {
return $q->where([
'Jobs.title OR Jobs.description LIKE' =>
"%" . $this->request->data('keywords') . "%"
]);
}]
);
}
$this->set('jobs',$jobs);
}
it works good if I'm approaching to areas via jobs
<?php foreach($jobs as $job): ?>
<option><?php echo $job->area->name; ?></option>
<?php endforeach; ?>
the problem is that by accessing through jobs table it repeats the the same area as many times as the it appears in jobs table instead of only once...
so if I'm trying to get direct access like this:
<?php foreach($areas as $area): ?>
<option><?php echo $area->name; ?></option>
<?php endforeach; ?>
it is not working because I'm submitted to jobs controller.
How can I overcome this problem?
I have three tables ServerScans, QueuedJobs, ReportMalware
ReportMalware has a column type which contain values like mail, abc.
I am querying ServerScans as
$scan = $this->ServerScans->get($id, [
'contain' => [
'QueuedJobs',
'QueuedJobs.ReportMalware',
],
]);
and in view separating malware report in two groups using for loop as
<h2>Mail report</h2>
<?php foreach($scan->queued_jobs->report_malware as $m): ?>
<?php if ($m->type == 'mail'): ?>
<?= $m->comment ?>
<?php endif; ?>
<?php endforeach;?>
<h2>Abc report</h2>
<?php foreach($scan->queued_jobs->report_malware as $m): ?>
<?php if ($m->type == 'abc'): ?>
<?= $m->comment ?>
<?php endif; ?>
<?php endforeach;?>
Which will take more time for execution.
What I want is to keep it in contain like
$scan = $this->ServerScans->get($id, [
'contain' => [
'QueuedJobs',
'QueuedJobs.ReportMalware.mail',
'QueuedJobs.ReportMalware.abc',
],
]);
Is there a way to achieve this and contain same model twice based on some filter criteria?
Yes, you can do so. See the docs here.
The same table can be used multiple times to define different types of
associations. For example consider a case where you want to separate
approved comments and those that have not been moderated yet:
Here is the example taken from the official documentation:
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('Comments')
->setConditions(['approved' => true]);
$this->hasMany('UnapprovedComments', [
'className' => 'Comments'
])
->setConditions(['approved' => false])
->setProperty('unapproved_comments');
}
}
public function outofstock(){
$this->loadModel('Store');
$store=$this->Store->find('all',array('fields' => array('Store.store_shortcode')));
$this->set('store',$store);
foreach($store as $store):
$store_shortcode= $store['Store']['store_shortcode'];
$item=$this->Item->find('all',array('conditions' => array($store_shortcode.' =' => 0 )));
foreach($item as $item1){
echo $item1['Item']['item_name'];
echo $store_shortcode;
}
endforeach;
$this->set('item',$item);
}
This is my controller part code .
I want to display echo $item1['Item']['item_name'] , $store_shortcode in view part . Actually in controller its displaying properly but in view part its not displaying . store_shortcode is GLF,DLLK,MKL . Item Name is ADASHG , GRAFGHJ, Store names or columns of item table and store_name of store table .
Controller ($items[ ]):
public function outofstock(){
$this->loadModel('Store');
$store=$this->Store->find('all',array('fields' => array('Store.store_shortcode')));
$this->set('store',$store);
foreach($store as $store):
$store_shortcode= $store['Store']['store_shortcode'];
$items[] =$this->Item->find('all',array('conditions' => array($store_shortcode.' =' => 0 )));
endforeach;
$this->set('items',$items);
}
In the view.ctp:
foreach($items as $item){
echo $item['Item']['item_name'];
}
But generally you have a a bad approach,
Do hasMany relational between the Store and Item model, and then easily with the help of a one query take data.
Use this in view file
foreach($store as $store):
$store_shortcode= $store['Store']['store_shortcode'];
$item=$this->Item->find('all',array('conditions' => array($store_shortcode.' =' => 0 )));
foreach($item as $item1){
echo $item1['Item']['item_name'];
echo $store_shortcode;
}
endforeach;
<?php
$radifHa = array();
foreach ($akhbars as $akhbar) {
$radif = array();
$radif[] = $this->Html->tag('h4', h($akhbar['Akhbar']['onvan']));
$radifInfo = array();
$radifInfo[] = h($akhbar['Akhbar']['created']);
$radifInfo[] = $this->Html->tag('span', ':', array('class' => 'separator'));
$radifInfo[] = 'by';
$radifInfo[] = $this->Html->link($akhbar['User']['esmekochak'], array('controller' => 'users', 'action' => 'view', $akhbar['User']['id']));
$radifInfo[] = $this->Html->tag('span', ':', array('class' => 'separator'));
$radifInfo[] = '0 comments';
$radif[] = array(implode(' ', $radifInfo), array('class' => 'post_info'));
$radif[] = $this->Html->div('desc_block', h($akhbar['Akhbar']['kholase']));
}
$radifHa[] = $radif;
if (!empty($radifHa)) {
echo $this->Html->div('blog_post', h($radifHa));
}
?>
I'm getting this error
Notice (8): Array to string conversion [CORE\Cake\View\Helper\HtmlHelper.php, line 928]
How can I fix this?
I'm in the index view of Akhbar.
Fixing with this alternative code, but I want to fix with the above code, can I?
<?php foreach ($akhbars as $akhbar): ?>
<div class="blog_post">
<h4><?php echo h($akhbar['Akhbar']['onvan']);?></h4>
<div class="post_info">
<?php echo h($akhbar['Akhbar']['created']);?>
<span class="separator">:</span>
By:<?php echo $this->Html->link($akhbar['User']['esmekochak'], array('controller' => 'users', 'action' => 'view', $akhbar['User']['id']));?>
<span class="separator">:</span>
0 comments
</div>
<div class="desc_block"><?php echo h($akhbar['Akhbar']['kholase']); ?></div>
</div>
<?php endforeach; ?>
You're not telling us exactly what line gives you that error (in the view, not the helper), but the error itself does tell you a lot about what is happening. You are simply passing an array where a string should be passed.
Html->div accepts Html->div(string, string, array), and you are doing
echo $this->Html->div('blog_post', h($radifHa));
Now, . The h() function says
string|array|object $text Text to wrap through htmlspecialchars.
Also works with arrays, and objects. Arrays will be mapped and have
all their elements escaped. Objects will be string cast if they
implement a __toString method. Otherwise the class name
will be used.
You are passing an array to h(), you will get a mapped array with the element escaped. You can see the code for that function here. Line 177, it returns an array. An since Html->div doesn't like arrays as second parameters, well, it complains.
What can you do? Don't use an array. Either flatten the array yourself before using div, or organize that string some other way instead of in an array (like concatenating the resulting strings in a foreach instead of storing them in $radifHa).
I have no idea how to handle these two separate paginations in the same view. Thanks for helping.
Controller code:
[...]
$this->Post->bindModel(array(
'hasAndBelongsToMany'=>array('Tag'),
'belongsTo'=>array('User')),false);
if($this->Session->check('Auth.User.id'))
$this->Post->bindModel(array(
'hasMany'=>array(
'UserVote'=>array('conditions'=>array('UserVote.user_id'=>$this->Session->read('Auth.User.id') )),
'Favorite'=>array('conditions'=>array('Favorite.user_id'=>$this->Session->read('Auth.User.id') ))
)), false);
$posts = $this->paginate($this->Post,array('Post.public'=>1,'Post.user_id'=>$uid));
$posts2 = $this->paginate($this->Post,array('Post.public'=>0,'Post.user_id'=>$uid));
$this->set('posts',$posts);
$this->set('posts2',$posts2);
[...]
Even though in most cases a right solution would be to rearchitect your UI to remove the need for double pagination, the following is a working solution:
First, in your controller you override Cake's paginate() function to look for paginator key:
/**
* Handles automatic pagination of model records.
*
* #param mixed $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel')
* #param mixed $scope Conditions to use while paginating
* #param array $whitelist List of allowed options for paging
* #return array Model query results
* #access public
* #link http://book.cakephp.org/view/165/Controller-Setup
*/
function paginate($object = null, $scope = array(), $whitelist = array(), $key = null) {
$results = parent::paginate($object, $scope, $whitelist);
if ($key) {
$this->params['paging'][$key] = $this->params['paging'][$object];
unset($this->params['paging'][$object]);
}
return $results;
}
Then
/**
* undocumented function
*
* #param string $key
* #return void
* #access public
*/
function _pageForPagination($by) {
$page = 1;
$samekey = isset($this->params['named']['by']) && $this->params['named']['by'] == $by;
$pageInUrl = isset($this->params['named']['page']);
if ($samekey && $pageInUrl) {
$page = $this->params['named']['page'];
}
$this->passedArgs['page'] = $page;
return $page;
}
/**
* FIXME: Wrapper for Cake's pagination
* Change pagination criteria on the fly (conditions, grouping, order, limit)
*
* #param string $model
* #param string $criteria
* #return void
* #author Andrew
*/
function _paginateBy($key) {
$this->User->unbindModel(array('hasMany' => array('UserImage')), false);
$this->paginate['User'] = am($this->User->getCriteria($key), array('page' => $this->_pageForPagination($key)));
return $this->paginate('User', array(), array(), $key);
}
Then use it like so in the controller:
$this->set('byJoinDate', $this->_paginateBy('random'));
In the model:
echo $paginator->prev('prev', array('model' => $by, 'class' => 'back'), null, array('model' => $by, 'class' => 'disabled back'));
I recently had to do this very thing because I had a single HTML page that had tabs on it and in each tab was a different paginated table of the same model with different conditions for each.
The way I worked around the problem was to create dummy models that derived from the model I wanted to paginate multiple times. Then I simply referenced those dummy models for my pagination.
Example:
Base Model
class Post extends appmodel { };
Dummy Models - it is important that they use the same table as the base model
class Posts1 extends Post { var $useTable = 'posts'; }
class Posts2 extends Post { var $useTable = 'posts'; }
In your controller
function multiview($id = null) {
$this->paginate['Posts1'] = array(
'conditions'=>array('Posts1.field'=>0),
'limit'=>5
);
$this->set('posts1', $this->paginate('Posts1'));
$this->paginate['Posts2'] = array(
'conditions'=>array('Posts2.field'=>1),
'limit'=>5
);
$this->set('posts2', $this->paginate('Posts2'));
}
Then in your view
Display first paginated data
<?php foreach ($posts1 as $post): ?>
Do Paginated row display here...
<?php endforeach; ?>
<!-- Shows the page numbers -->
<?php echo $this->Paginator->numbers(array('model'=>'Posts1')); ?>
<!-- Shows the next and previous links -->
<?php echo $this->Paginator->prev('« Previous', null, null, array('class' => 'disabled')); ?>
<?php echo $this->Paginator->next('Next »', null, null, array('class' => 'disabled')); ?>
<!-- prints X of Y, where X is current page and Y is number of pages -->
<?php echo $this->Paginator->counter(); ?>
Display second paginated data
<?php foreach ($posts2 as $post): ?>
Do Paginated row display here...
<?php endforeach; ?>
<!-- Shows the page numbers -->
<?php echo $this->Paginator->numbers(array('model'=>'Posts2')); ?>
<!-- Shows the next and previous links -->
<?php echo $this->Paginator->prev('« Previous', null, null, array('class' => 'disabled')); ?>
<?php echo $this->Paginator->next('Next »', null, null, array('class' => 'disabled')); ?>
<!-- prints X of Y, where X is current page and Y is number of pages -->
<?php echo $this->Paginator->counter(); ?>
As the Pagination helper relies on the $controller->pagination field I think this is impossible. A quick hack would be to convert one or both of the paginations to Ajax-based ones. The pagination helper works very well with the Ajax helper.
Is it a good idea to have two separate paginations on a page? It looks like You're trying to paginate through public posts and non-public posts - which surely can't be done at the same time? I.e. the user won't ever be on page 3 of the public posts and page 2 of the non-public posts at the same time. The only case this would really work is via AJAX with the pagination occouring in page as suggested by matiasf.
Would it not be a better solution to just show the first x public and non public posts in the current view and create a new view to show the pagination for each accessed by a link e.g. "view all public posts" and "view all non public posts"?