I have a "little" problem with the Pagination system of CakePHP (1.2). Here is the query:
$this->paginate = array (
'fields' => array (
'Content.slug',
'Content.title',
'Content.resume',
'Content.format',
'Content.image',
'Content.video',
'Criteria.name'
),
'conditions' => $conditions,
'order' => 'Content.created DESC',
'limit' => 10,
'contain' => array (
'Category',
'Criteria',
)
);
$this->set("PRODUCTS", $this->Paginate("Content"));
And the code of view:
<?php $total_pages = (int)$paginator->counter(array('format' => '%pages%')); ?>
<?php if($total_pages > 1){ ?>
<div class="paginar">
<div class="next_pre_arrow">
<?=$paginator->prev("Anterior", array("class" => "pre", "escape" => false))?>
<?=$paginator->next("Siguiente", array("class" => "next", "escape" => false))?>
<div class="pages">
<span>Página</span> <?=$paginator->numbers(array('separator' => ' | '))?>
</div>
</div>
</div>
<?php } ?>
What is the problem? The pagination works OK but with a little problem. In the "next" and "prev" buttons, and in the page numbers, the URL is truncated, deleting the last param, for example:
"http://www.domain.com/controller-name/caction-name/option-1/option-2"
Show paging links with this URL:
"http://www.domain.com/controller-name/caction-name/option-1/page:2"
NOT the correct:
"http://www.domain.com/controller-name/caction-name/option-1/option-2/page:2"
What is the cause of this?
I think you can customize the links that are generated by the Paginator helper using the options() method.
Specifically, you can use $options['url'] to pass a custom URL, as if you were setting parameters of a link() call:
$paginator->options(array(
'url' => array(
'controller' => 'YourController',
'action' => 'your_action'
'param1' => 'value_1',
'param2' => 'value_2',
)));
Related
I have a project model which has many Invoices and Credits. I'm trying to paginate both the Invoices and Credits info in a particular function in my ProjectsController.
No problems getting the correct info paginated:
App::Import('Model', 'Invoice');
$this->Invoice = new Invoice;
$this->Paginator->settings = array(
'conditions' => array('Invoice.paid' => 1, 'Invoice.project_id'=>$id),
'limit' => 5
);
$paidInvoices = $this->paginate('Invoice');
App::Import('Model', 'Credit');
$this->Credit = new Credit;
$this->Paginator->settings = array(
'conditions' => array('Credit.project_id' => $id),
'limit' => 3
);
$credits = $this->paginate('Credit');
The problem is that even though I specify the model like it says in the docs 6 page links will appear: there should only be 2. There will always be a next link even though there might be no page to visit.
echo '<div class="right pagination">'.$this->Paginator->first('<< first', array('model'=>'Credit')).
$this->Paginator->prev('< ' . __('previous'), array('model'=>'Credit'), null, array('class' => 'prev disabled')).
$this->Paginator->numbers(array('separator' => '', 'model'=>'Credit')).
$this->Paginator->next(__('next', array('model'=>'Credit')) . ' >', array('model'=>'Credit'), null, array('class' => 'next disabled')).
$this->Paginator->last('last >>', array('model'=>'Credit')).'</div>';
It looks like CakePHP is getting confused when calculating the number of available results. The $settings property seems like the culprit.
You have to include the key for you model name, as follows:
$this->Paginator->settings['Invoice'] = array(
'conditions' => array('Invoice.paid' => 1, 'Invoice.project_id'=>$id),
'limit' => 5
);
and
$this->Paginator->settings['Credit'] = array(
'conditions' => array('Credit.project_id' => $id),
'limit' => 3
);
I had a search page that filtered correctly, but when it paginated you would lose the data since I was POSTing it. So I switched it to a GET, but I can't figure out how to add the http_build_query( $params ) that I passed back to the paginator.
I've tried setting query params in the paginator url in options, but with no luck and the API doesn't mention adding the query params.
How do I set the search results query params so the different pages know what they were being filtered on? So a search on name=steve and company=SomeCompany is maintained through page 2, 3, 4, with 10 results each, and doesn't reset to show all non-filtered 100 results.
Simple Example Pagination in Controller
$this->paginate = [
'limit' => 5,
'order' => [ 'CollectionAgencyAgent.id' => 'desc' ]
];
return $this->paginate( $this->CollectionAgencyAgent, $conditions );
VIEW with Pagination
<ul class="pagination <?php echo $class ?>">
<?php
$this->Paginator->options( [
'url' => [
'controller' => ''
]
] );
?>
<?php echo $this->Paginator->prev( __( '« Previous' ), [
'escape' => false,
'tag' => 'li',
'class' => 'arrow',
'disabledTag' => 'a'
] ); ?>
<?php echo $this->Paginator->numbers( [
'separator' => '',
'tag' => 'li',
'currentTag' => 'a'
] ); ?>
<?php echo $this->Paginator->next( __( 'Next »' ), [
'escape' => false,
'tag' => 'li',
'class' => 'arrow',
'disabledTag' => 'a'
] ); ?>
</ul>
The paginator class automatically merges the current request parameters:
public function beforeRender($viewFile) {
$this->options['url'] = array_merge($this->request->params['pass'], $this->request->params['named']);
if (!empty($this->request->query)) {
$this->options['url']['?'] = $this->request->query;
}
parent::beforeRender($viewFile);
}
So the short answer is to do nothing, and that'll just work.
The reason it doesn't work with the code in the question is that this call:
$this->Paginator->options( [
'url' => [
'controller' => ''
]
] );
Which will wipe out the Paginator's run time url options. So, to prevent the problem in the question - just delete that call (and then, probably fix the routing problem which prompted you to add it =)).
I have following problem and i tested it with data from the original documentation of the zend framework 2. I dont know if i miss something or if it is just a missing functionality.
In the following example i create a navigation with two levels and then print the navigation labels with echo. As you can see i set an order for both first level pages and for two second level pages. If you look at my output you can see that only the first level pages are in right order. The second level pages arent in right order.
For every level you put in a navigation greater than level one, the order isnt changed.
Anybody of you have the same problem or can tell me what iam doing wrong? Or is this a not given functionality of the zend-navigation module?
Thanks
Script
$container = new \Zend\Navigation\Navigation(
array(
array(
'label' => 'ACL page 1 (guest)',
'uri' => '#acl-guest',
'resource' => 'nav-guest',
'order' => 777,
'pages' => array(
array(
'label' => 'ACL page 1.1 (foo)',
'uri' => '#acl-foo',
'resource' => 'nav-foo',
'order' => 666
),
array(
'label' => 'ACL page 1.2 (bar)',
'uri' => '#acl-bar',
'resource' => 'nav-bar',
),
array(
'label' => 'ACL page 1.3 (baz)',
'uri' => '#acl-baz',
'resource' => 'nav-baz',
),
array(
'label' => 'ACL page 1.4 (bat)',
'uri' => '#acl-bat',
'resource' => 'nav-bat',
'order' => 111
),
),
),
array(
'label' => 'ACL page 2 (member)',
'uri' => '#acl-member',
'resource' => 'nav-member',
'order' => 555
)
)
);
foreach ($container as $page) {
echo $page->label."<br>";
if ($page->hasPages()) {
foreach ($page->getPages() as $page2) {
echo $page2->label."<br>";
}
}
}
die("ASD");
Output
ACL page 2 (member)
ACL page 1 (guest)
ACL page 1.1 (foo)
ACL page 1.2 (bar)
ACL page 1.3 (baz)
ACL page 1.4 (bat)
ASD
Method $page->getPages() return Array (docs). Elements are ordered only if list is Navigation object.
Try:
foreach ($container as $page) {
echo $page->label."<br>";
if ($page->hasPages()) {
$subpages = new \Zend\Navigation\Navigation($page->getPages());
foreach ($subpages as $page2) {
echo $page2->label."<br>";
}
}
}
I'm not sure that i've written something intelligibile in the title either.
I'll try to explain with some codes line. First some information.
I'm working with CakePhP and this problem comes up while creating the arrays for the actions allowed.
Long story short, i'm using an ACL to check whenever a page is loaded if the current user has access to the actions available on that page. This is an example:
//Controller
$role = $this->User->Role;
$role->id = $this->Auth->user('Role.id');
$actionsMenu = array();
$actionsMenu['Files'] = array(
'label' => 'Manage Files',
'controller' => 'project_files',
'action' => 'index',
'parameters' => $id,
'authorized' => $this->Acl->check($role, 'ProjectFiles')
);
$actionsMenu['Groups'] = array(
'label' => 'Manage Groups',
'controller' => 'groups',
'action' => 'index',
'parameters' => $id,
'authorized' => $this->Acl->check($role, 'Groups')
);
In the view I just loop and if the "authorized" is set to true, i'll print the related button.
Now, the problem rise when i've more that one parameter to pass. This is another snippet that follows the one up there:
//Controller [following]
$this->Project->id = $id;
if ($this->Project->field('archived')) {
$actionsMenu['Archiviation'] = array(
'label' => 'Restore',
'controller' => 'projects',
'action' => 'archiviation',
'parameters' => array($id, 0),
'authorized' => $this->Acl->check($role, 'controllers/Projects/archiviation')
);
} else {
$actionsMenu['Archiviation'] = array(
'label' => 'Archive',
'controller' => 'projects',
'action' => 'archiviation',
'parameters' => array($id, 1),
'authorized' => $this->Acl->check($role, 'controllers/Projects/archiviation')
);
}
This is what you can find in the views:
//View
<div class="actions">
<h3><?php echo __('Actions'); ?></h3>
<ul>
<li><?php echo $this->Html->link(__('<- Projects Management'), array('action' => 'index')); ?></li>
<li> </li>
<?php foreach($actionsMenu as $action) : ?>
<?php if($action['authorized']) : ?>
<li><?php echo $this->Html->link(__($action['label']), array('controller' => $action['controller'], 'action' => $action['action'],
is_array($action['parameters']) ? implode(', ', $action['parameters']) : $action['parameters']
)); ?></li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
I thought the array format was the most CakePhP friendly way to pass the values just to discover that in case of multiple parameters, cake tend to prefer an associative array.
The problem here is that CakePhP read that implode as a whole parameter composed by a string.
This is an example:
<!--HTML-->
Restore
What I want to achieve is that the values separated by the comma should be read as different variables.
Honestly I never run in a situation like this and I've neither idea of what to look for on google to find something fitting (being a non-native english speaking isn't helping) so any suggestion is welcome.
Edit1
To clarify, the code listed in the second box (starting with Controller [following]) is the one that is causing problems.
The previous box just build the array with a single parameters that match the simple structure of CakePhP for building link but the second block will need to pass a second parameter. If the values was static one could simply type something like this:
//CakePhP HTML::Link
echo $this->Html->link(
'text of link',
array(
'controller' => 'controller_name',
'action' => 'action_name',
'parameter1', 'parameter2'
));
If I send the list of parameters as string, Cake are considering them a single parameter, reading it like (string)("'parameter1', 'parameter2'").
The same happens with the code i've written above in which i'm converting the array of values to string with implode().
So what I'm asking for, is if there a function, option or practice that I'm missing.
Edit2
As the user user221931 pointed out, the CakePhP function should support multiple parameters as array in the form of array('parameter1', 'parameter2', 'paramenterN').
So i've tried just removing the is_array() check and simply pass the value of $action['parameters']. The view section now look like this:
<?php echo $this->Html->link(__($action['label']), array(
'controller' => $action['controller'],
'action' => $action['action'],
//is_array($action['parameters']) ? implode(', ', $action['parameters']) : $action['parameters']
$action['parameters']
)); ?>
But i've got a warning as follows:
rawurlencode() expects parameter 1 to be string, array given
Going to open the context of the warning, seems like CakePhP is reading the information provided as follows:
$params = array(
'controller' => 'projects',
'action' => 'archiviation',
'plugin' => null,
'pass' => array(
(int) 0 => array(
(int) 0 => '1',
(int) 1 => (int) 1
)
),
'named' => array()
)
Honestly I'm lost here.
I've tried changing the second value of the array to a string too and passing an associative array instead of a numeric just to try something obvious but the error persist and the link comes out truncated without parameters.
The correct format to use Html::link()
echo $this->Html->link(
'text of link',
array(
'controller' => 'users', //Or any controller name
'action' => 'view', //Or any action
1, //Several parameters
'test1', //go here
'test2'
)
);
If you don't know the number of parameters beforehand, you only need to array_merge your fixed array with the array of dynamic parameters.
Assuming $arrayOfParameters holds array('test1', 'test2', 'test3')
$urlArray = array_merge(
array('controller' => 'users', 'action' => 'view'),
$arrayOfParameters
);
echo $this->Html->link('text of link', $urlArray);
Additionally you could implode your array of parameters as a url, i.e:
$urlString = implode('/', $arrayOfParameters); //creates a string 'test1/test2/test3'
echo $this->Html->link('text of link', array(
'controller' => 'users',
'action' => 'view',
$urlString //One parameter that will be looking as many
));
I have the following method which takes a query to search my notes:
function search( $q = null )
{
if ( $q == null )
{
$this->redirect(array('action' => 'index'));
}
$this->paginate = array(
'limit'=>5,
'order'=>'Note.datetime DESC',
'conditions' => array(
'OR' => array(
'Note.title LIKE' => '%'. $q . '%',
'Note.content LIKE' => '%'. $q . '%'
)
),
'Note.status'=>1
);
$this->set('notes', $this->paginate());
$this->render('index');
}
As you can see it takes a single parameter called 'q' which is used to query the model data.
I have hooked up this to the router like so:
Router::connect('/notes',
array('controller'=>'notes','action'=>'index', 'page' => 1),
array(
'pass' => array('page')
)
);
Router::connect('/notes/page/:page',
array('controller' => 'notes', 'action' => 'index'),
array(
'pass' => array('page'),
'page' => '[1-9]+'
)
);
Router::connect('/notes/search/:page/:q',
array('controller'=>'notes','action'=>'search', 'page' => 1),
array(
'pass' => array('page','q')
)
);
Router::connect('/notes/search/:q/page/:page',
array('controller' => 'notes', 'action' => 'search'),
array(
'pass' => array('page','q'),
'page' => '[1-9]+'
)
);
This way I should be getting urls like:
domain.com/notes - loads page 1 of notes
domain.com/notes/page/2 - loads page 2 of notes
domain.com/notes/search/Hello - searches for Hello in notes
domain.com/notes/search/Hello/page/2 - shows page 2 of the above search
The pager in the view looks like:
<?php if(isset($this->request->params['named']['q'])) { ?>
<?php $this->Paginator->options(array('url'=>array('controller' => 'notes', 'action' => $action, 'q' => $this->request->params['named']['q']))); ?>
<?php } else { ?>
<?php $this->Paginator->options(array('url'=>array('controller' => 'notes', 'action' => $action))); ?>
<?php } ?>
It works fine for the index method, but for the search method it is getting confused as when I do a search it's not matching the pager with the route as expected. For example I'm getting urls like domain.com/notes/search/2/:q
Also I don't really like having to wrap the paginator options in an if statement so if I can get it to figure out the url automatically that would be awesome as it's messy having to do this and seems to be the cause of the above problems.
I have connected the named parameter at the top of the router like so:
Router::connectNamed(array('q'));
In the end I opted to make my search working using POST instead of GET so that everything is handled sever-side instead of doing it with messy url rewrites and trying to be clever.
Here's what I made the form look like:
<?php echo $this->Form->create('search', array('url'=>array('controller'=>'notes','action'=>'search'),'class'=>'search')); ?>
<label class="placeholder" for="q">Search</label>
<?php if( isset($q) ) { $term = $q; } else { $term = ''; } ?>
<?php echo $this->Form->input('q', array('label'=>false,'id'=>'q','value'=>$term)); ?>
<button type="submit" class="btn ss-icon ss-search"></button>
<?php echo $this->Form->end(); ?>
the search method:
function search()
{
if ($this->request->is('post')) {
$this->Session->write('q', $this->request->data['search']['q']);
$this->redirect(array('action' => 'search'));
} else {
$q = $this->Session->read('q');
$this->paginate = array(
'limit'=>5,
'order'=>'Note.datetime DESC',
'conditions' => array(
'OR' => array(
'Note.title LIKE' => '%'. $q . '%',
'Note.content LIKE' => '%'. $q . '%'
)
),
'Note.status'=>1
);
$this->set('q',$q);
$this->set('action','search');
$this->set('notes', $this->paginate());
$this->render('index');
}
}
and the routes:
Router::connect('/notes/search',
array('controller'=>'notes','action'=>'search', 'page' => 1),
array(
'pass' => array('page')
)
);
Router::connect('/notes/search/page/:page',
array('controller' => 'notes', 'action' => 'search'),
array(
'pass' => array('page'),
'page' => '[1-9]+'
)
);
and I clean up the session if any other page but the search method is being used in the AppController:
if(strpos($this->here, Router::url(array('controller'=>'notes','action'=>'search'))) === 0 ) {
//echo 'yes';
} else {
$this->Session->delete('q');
}
Which gives me urls like:
domain.com/notes - loads page 1 of notes
domain.com/notes/page/2 - loads page 2 of notes
domain.com/notes/search - searches for Hello in notes (stored in session)
domain.com/notes/search/page/2 - shows page 2 of the above search