get next & previous id record in database on Yii - php

I need next & previous id record in database on Yii framework to make navigation buttons next and back ?

I added following functions in my model in Yii2:
public function getNext() {
$next = $this->find()->where(['>', 'id', $this->id])->one();
return $next;
}
public function getPrev() {
$prev = $this->find()->where(['<', 'id', $this->id])->orderBy('id desc')->one();
return $prev;
}

I made a function to get those ids your looking for. I suggest you to declare it in the model:
public static function getNextOrPrevId($currentId, $nextOrPrev)
{
$records=NULL;
if($nextOrPrev == "prev")
$order="id DESC";
if($nextOrPrev == "next")
$order="id ASC";
$records=YourModel::model()->findAll(
array('select'=>'id', 'order'=>$order)
);
foreach($records as $i=>$r)
if($r->id == $currentId)
return isset($records[$i+1]->id) ? $records[$i+1]->id : NULL;
return NULL;
}
So to use it all you have to do do is this:
YourModel::getNextOrPrevId($id /*(current id)*/, "prev" /*(or "next")*/);
It will return the corresponding id of the next or previous record.
I didn't test it, so give it a try and if something goes wrong please let me know.

Make a private var that is used to pass info to other functions.
In Model:
class Model1 .....
{
...
private _prevId = null;
private _nextId = null;
...
public function afterFind() //this function will be called after your every find call
{
//find/calculate/set $this->_prevId;
//find/calculate/set $this->_nextId;
}
public function getPrevId() {
return $this->prevId;
}
public function getNextId() {
return $this->nextId;
}
}
Check the code generated in the ViewDetal link and modify for the Prev/Net links in the _view file using
$model(or $data)->prevId/nextId
in the array('id'=>#) section.

Taking the original answer and adapting it for Yii2 with a little clean up:
/**
* [nextOrPrev description]
* #source http://stackoverflow.com/questions/8872101/get-next-previous-id-record-in-database-on-yii
* #param integer $currentId [description]
* #param string $nextOrPrev [description]
* #return integer [description]
*/
public static function nextOrPrev($currentId, $nextOrPrev = 'next')
{
$order = ($nextOrPrev == 'next') ? 'id ASC' : 'id DESC';
$records = \namespace\path\Model::find()->orderBy($order)->all();
foreach ($records as $i => $r) {
if ($r->id == $currentId) {
return ($records[$i+1]->id ? $records[$i+1]->id : NULL);
}
}
return false;
}

My implementation is based on SearchModel.
Controller:
public function actionView($id)
{
// ... some code before
// Get prev and next orders
// Setup search model
$searchModel = new OrderSearch();
$orderSearch = \yii\helpers\Json::decode(Yii::$app->getRequest()->getCookies()->getValue('s-' . Yii::$app->user->identity->id));
$params = [];
if (!empty($orderSearch)){
$params['OrderSearch'] = $orderSearch;
}
$dataProvider = $searchModel->search($params);
$sort = $dataProvider->getSort();
$sort->defaultOrder = ['created' => SORT_DESC];
$dataProvider->setSort($sort);
// Get page number by searching current ID key in models
$pageNum = array_search($id, array_column($dataProvider->getModels(), 'id'));
$count = $dataProvider->getCount();
$dataProvider->pagination->pageSize = 1;
$orderPrev = $orderNext = null;
if ($pageNum > 0) {
$dataProvider->pagination->setPage($pageNum - 1);
$dataProvider->refresh();
$orderPrev = $dataProvider->getModels()[0];
}
if ($pageNum < $count) {
$dataProvider->pagination->setPage($pageNum + 1);
$dataProvider->refresh();
$orderNext = $dataProvider->getModels()[0];
}
// ... some code after
}
OrderSearch:
public function search($params)
{
// Set cookie with search params
Yii::$app->response->cookies->add(new \yii\web\Cookie([
'name' => 's-' . Yii::$app->user->identity->id,
'value' => \yii\helpers\Json::encode($params['OrderSearch']),
'expire' => 2147483647,
]));
// ... search model code here ...
}
PS: be sure if you can use array_column for array of objects.
This works good in PHP 7+ but in lower versions you got to extract id by yourself. Maybe it's good idea to use array_walk or array_filter in PHP 5.4+

Full implemenentation with performance improvement by using DB engine/optimization (when id acts as primary key):
Model:
public static function getNextPrevId($currentId)
{
$queryprev = new Query();
$queryprev->select('max(id)')->from(self::tableName())->where('id<:id',['id'=>$currentId]);
$querynext = new Query();
$querynext->select('min(id)')->from(self::tableName())->where('id>:id',['id'=>$currentId]);
return [ $queryprev->scalar(), $querynext->scalar()];
}
Controller:
public function actionView($id) {
return $this->render('view', [
'model' => $this->findModel($id),
'nextprev' => YourModel::getNextPrevId($id),
]);
}
View:
<?= !is_null($nextprev[0]) ? Html::a('⇦', ['view', 'id' => $nextprev[0]], ['class' => 'btn btn-primary']) : '' ?>
<?= !is_null($nextprev[1]) ? Html::a('⇨', ['view', 'id' => $nextprev[1]], ['class' => 'btn btn-primary']) : '' ?>

The previous solutions are problematic when you get the the first or last record and they are making multiple calls to the database. Here is my working solution which operates on one query, handles end-of-table and disables the buttons at end-of-table:
Within the model:
public static function NextOrPrev($currentId)
{
$records = <Table>::find()->orderBy('id DESC')->all();
foreach ($records as $i => $record) {
if ($record->id == $currentId) {
$next = isset($records[$i - 1]->id)?$records[$i - 1]->id:null;
$prev = isset($records[$i + 1]->id)?$records[$i + 1]->id:null;
break;
}
}
return ['next'=>$next, 'prev'=>$prev];
}
Within the controller:
public function actionView($id)
{
$index = <modelName>::nextOrPrev($id);
$nextID = $index['next'];
$disableNext = ($nextID===null)?'disabled':null;
$prevID = $index['prev'];
$disablePrev = ($prevID===null)?'disabled':null;
// usual detail-view model
$model = $this->findModel($id);
return $this->render('view', [
'model' => $model,
'nextID'=>$nextID,
'prevID'=>$prevID,
'disableNext'=>$disableNext,
'disablePrev'=>$disablePrev,
]);
}
Within the view:
<?= Html::a('Next', ['view', 'id' => $nextID], ['class' => 'btn btn-primary r-align btn-sm '.$disableNext]) ?>
<?= Html::a('Prev', ['view', 'id' => $prevID], ['class' => 'btn btn-primary r-align btn-sm '.$disablePrev]) ?>

Related

In CakePhp, how can I retrieve the value of only one column from my database?

I am new to CakePHP but I have been using PHP for a while. I am trying to create a helper that would provide the level of access of a user (ACL).
Here is my ACLHelper.php so far
<?php
namespace App\View\Helper;
use Cake\View\Helper;
use Cake\ORM\TableRegistry;
class ACLHelper extends Helper{
public function getACL($id, $acl_field, $level){
$members = TableRegistry::get('groups_member');
$group = $members->find()->where(['user_id' => $id]);
$acls = TableRegistry::get('acls');
$acl = $acls->find('all', [ 'fields' => $acl_field ])->where(['group_id' => $group->first()->group_id]);
return $acl->first();
}
}
I call this function in my view this way
<?= $this->ACL->getACL($user->id, 'is_items', '4') ?>
And this is the output
{ "is_items": "4" }
What I need is the function to return true or false if the value of the field equals or is higher then the value of $level provided to the function. Now if I do this :
<?= $this->ACL->getACL($user->id, 'is_items', '4')->is_item ?>
it will return just the value. My problem is that I do not want to specify the field twice.
Thanks in advance for any help
public function getACL($id, $acl_field, $level){
$members = TableRegistry::get('groups_member');
$group = $members->find()->where(['user_id' => $id]);
$acls = TableRegistry::get('acls');
// Get the first ACL record right here
$acl = $acls->find('all', [ 'fields' => $acl_field ])->where(['group_id' => $group->first()->group_id])->first();
// Compare the requested field against the provided level
return $acl->$acl_field >= $level;
}

Modify data before pagination in CakePhp

I'm trying to create an Api using cakephp.
I generate a json on server and it works fine , but I tired to use pagination and I got a problem.
in the first case I take the image's path and I encode it to base64 and I generate json => works
in the second case I defined the pagination by the limits and the max and I kept the same code but as a result the image field is still the path from the database and it's not encoded
this my code in my controller :
class PilotsController extends AppController {
public $paginate = [
'page' => 1,
'limit' => 5,
'maxLimit' => 5
];
public function initialize() {
parent::initialize();
$this->loadComponent('Paginator');
$this->Auth->allow(['add','edit','delete','view','count']);
}
public function view($id) {
$pilot = $this->Pilots->find()->where(['Pilots.account_id' => $id], [
'contain' => ['Accounts', 'Pilotlogs']
]);
foreach ($pilot as $obj) {
if ($obj->image_pilot!= NULL) {
$image1 = file_get_contents(WWW_ROOT.$obj->image_pilot);
$obj->image_pilot = base64_encode($image1);
}
}
$this->set('pilot', $this->paginate($pilot));
$this->set('_serialize', ['pilot']);
}
}
If I remove the pagination from the code it works fine . Any idea how to fix it ??
I'd suggest to use a result formatter instead, ie Query::formatResults().
So you'll have something like this :
public function view($id) {
$pilot = $this->Pilots->find()
->where(['Pilots.account_id' => $id], [
'contain' => ['Accounts', 'Pilotlogs']]);
->formatResults(function($results) {
return $results->map(function($row) {
$image1 = file_get_contents(WWW_ROOT.$row['image_pilot']);
$row['image_pilot'] = base64_encode($image1);
return $row;
});
});
}
You can simply first paginate the data and then get the array values and after that modify that data as you want. Check this
public function view($id) {
$pilot = $this->Pilots->find()->where(['Pilots.account_id' => $id], [
'contain' => ['Accounts', 'Pilotlogs']
]);
$pilot = $this->paginate($pilot);
$pilot = $pilot->toArray();
foreach ($pilot as $obj) {
if ($obj->image_pilot!= NULL) {
$image1 = file_get_contents(WWW_ROOT.$obj->image_pilot);
$obj->image_pilot = base64_encode($image1);
}
}
$this->set('pilot', $pilot);
$this->set('_serialize', ['pilot']);
}

Yii2 FullCalendar: how to add link button

I am using FullCalendar , its work perfectly , but i need to add href to the button className='btn' when i click on redirect to view page .
my code in controller :
public function actionIndex()
{
$events = event::find()->all();
$taskes=[];
foreach ($events as $eve)
{
$event1 = new \yii2fullcalendar\models\Event();
$patient = patient::findOne($eve->patient_id);
$event1->className='btn'; // this button that i need to add link to : ['site/view', 'id' => $id ]
$event1->id = $eve->id;
$event1->title = $patient->patient_name;
$event1->start = $eve->event_date;
$taskes[] = $event1;
}
return $this->render('index', [
'events'=>$taskes,
]);
}
I think you can make like this:
...
$event1->url = Url::to(['site/view', 'id' => $id ]);
...
Look in the documentation - http://fullcalendar.io/docs/event_data/Event_Object/

Yii2 checkboxlist broken down into categories (nested sets)

I seem to be having some trouble creating a form input that allows checkboxlists and nested sets to work together.
What I'd like, is something exactly like what bookbub does:
http://i.imgur.com/PfpgSf5.jpg
Right now in my database I have it structured as follows:
Category table
- id
- name
- parent_id
Basically, my idea is to display everything on the _form that has parent_id as null as a heading (no checkbox) and everything that has a parent_id as a checkbox under the appropriate heading.
However, the only solution that I can get that's close doesn't seem to allow me to have checkboxes already checked if we're updating a user's preferences. It does however display things exactly how I would like. Here's what I have so far:
ProfileReader's (ProfileReader is my model that extends users to hold their preferences) _form:
<?php
$primaryCategories = (new Category)->getPrimaryCategories();
//$selectedCategories = yii\helpers\ArrayHelper::map($model->categories, 'id', 'name');
foreach ($primaryCategories as $pc) {
echo '<p>'.$pc->name.'</p>';
if ($pc->subCategories) {
//the following never fully worked. It doesn't automatically check the boxes for relations that
//are already setup. You need to somehow use $model->categories[#] and 'id' for that to work
//echo $form->field($model->categories[#], 'id')->label(false)
echo $form->field($pc, 'subCategories[' . $pc->id . '][]')->label(false)
->checkboxList(yii\helpers\ArrayHelper::map($pc->subCategories, 'id', 'name'),
['separator' => '<p>']);
}
}
?>
ProfileReaderController:
public function actionUpdate()
{
$model = $this->findModel(\Yii::$app->user->identity->id);
if ($model == null) {
$model = new ProfileReader();
$model->user_id = \Yii::$app->user->identity->id;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
//link the categories to the pen name
$categories = Yii::$app->request->post()['Category']['subCategories'];
$model->unlinkAll('categories', true);
foreach ($categories as $category) {
if ($category)
foreach ($category as $c) {
$c = (new Category)->findOne($c);
$model->link('categories', $c);
}
}
return $this->redirect(['update']);
} else {
return $this->render('update', [
'model' => $model,
]);
}
}
ProfileReader:
public function getCategories()
{
return $this->hasMany(Category::className(), ['id' => 'category_id'])
->viaTable('user_category', ['user_id' => 'user_id']);
}
Does anyone have any clue how I can make this work? Is it even possible in Yii2 with activeform?
Okay, after many hours I finally figured it out. Posting my resulting code here so that it may help someone else. Don't ask me to explain it as I don't fully get it myself :P
It might also be a good idea to do some testing on it too before throwing it into a live environment, I haven't done any yet.
update action:
/**
* Updates an existing ProfileReader model.
* If update is successful, the browser will be redirected to the 'view' page.
* #param integer $id
* #return mixed
*/
public function actionUpdate()
{
$model = $this->findModel(\Yii::$app->user->identity->id);
if ($model == null) {
$model = new ProfileReader();
$model->user_id = \Yii::$app->user->identity->id;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
//unlink the categories first to avoid duplicates
$model->unlinkAll('categories', true);
//link the categories to the pen name
foreach ($model->categoriesArray as $pc) {
if ($pc) {
foreach ($pc as $sc) {
$sc = (new Category)->findOne($sc);
$model->link('categories', $sc);
}
}
}
return $this->redirect(['update']);
} else {
//get the categories and separate them into groups based on parent_id
foreach ($model->categories as $c) {
$model->categoriesArray[$c->parent_id][] = $c;
}
return $this->render('update', [
'model' => $model,
]);
}
}
ProfileReader model (had to add a variable):
public $categoriesArray;
_form:
<label class="control-label">Categories</label>
<?php
$allCategories = (new Category)->getOrderedCategories();
foreach ($allCategories as $pc) {
echo '<p>'.$pc['name'].'</p>';
echo $form->field($model, 'categoriesArray['.$pc['id'].'][]')->label(false)
->checkboxList(yii\helpers\ArrayHelper::map($pc['subCategories'], 'id', 'name'),
['separator' => '<p>']);
}
?>

Laravel error when hasOne has nothing

I am trying to pre-populate some fields in a form and I'm new to relationships.
My controller:
public function index($supplierId) {
$Supplier = new Supplier;
$supplierData = Supplier::find($supplierId);
$supplierData->countryId = ($supplierData->countryId == 0 ? 258 : $supplierData->countryId);
$supplierData->writtenLanguageId = ($supplierData->writtenLanguageId == 0 ? 1 : $supplierData->writtenLanguageId);
$supplierData->paymentTermsId = ($supplierData->paymentTermsId == 0 ? 5 : $supplierData->paymentTermsId);
$countries = Countries::lists('country', 'id');
$languages = Languages::lists('language', 'id');
$paymentTerms = PaymentTerms::lists('term', 'id');
$leadTimes = Leadtimes::lists('leadtime', 'id');
return View::make('supplier.supplier', array(
'supplierData' => $supplierData,
'countries' => $countries,
'languages' => $languages,
'paymentsTerms' => $paymentTerms,
'leadtimes' => $leadTimes
));
}
My model:
class Supplier extends Eloquent {
protected $table = 'suppliers';
public function email() {
return $this->hasOne('SupplierEmail', 'supplierId');
}
public function creditLimits() {
return $this->hasOne('SupplierCreditLimits', 'supplierId');
}
public function website() {
return $this->hasOne('SupplierWebsite', 'supplierId');
}
}
The problem:
<div class='col-xs-12 col-md-6'>{{Form::text('website', $supplierData->website->website, array('class' => 'form-control input-sm'))}}</div>
When there is no row (there is no record), I get:
Trying to get property of non-object (View: C:\wamp\vhosts\view\laravel\app\views\supplier\supplier.blade.php)
How do I get this to work properly?
In your view, use isset to check the value first:
<div class='col-xs-12 col-md-6'>
{{Form::text('website',
isset($supplierData->website->website) ? $supplierData->website->website : '',
array('class' => 'form-control input-sm'))
}}
</div>
Or, better yet, handle this logic in your controller and pass the result to the view:
$supplierData->URL = isset($supplierData->website->website) ? $supplierData->website->website : '';

Categories