Yii2 ValidatePage to give empty output on wrong page number - yii

I am trying to fetch a list of products from a table using my yii2 app and send it accross as a json to lazyload on scroll from the front end. I am using the searchmodel class. Now when data ends, last page data is being send again, i.e., if i have hundred records, the calls for page numbers above 5 would repeatedly send the same data as page number 4. How do i prevent it.
PS: Confused about the usage of the validatePage flag reading the documentation.
Here is my code of the controller.
public function actionAjaxIndex()
{
$searchModel = new productsS();
$response = (object) ['status' => 0];
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$response->status = 1;
$response->data = array();
foreach($dataProvider->models as $row){
foreach($row as $key=>$value){
$customerDetail[$key] = $value;
}
array_push($response->data, $customerDetail);
}
return json_encode($response);
}
Can somebody help with the best possible solution to go ahead.

You need to disable $validatePage for your data provider. This setting overwrites page if it is outside of range (so if you have 4 pages of records, but you request 5th page, pagination will automatically switch to 4th page - every page outside of range will display results for last page).
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->pagination->validatePage = false;

Related

popstate keeps using last state

I got a simple code when users are filtering brands, categories etc..
Everything works as it should.
BUT when you for example want to go back twice, the first popstate returns the last page perfectly. But when you go back again, it still returns the last page, so it loads it again. And not the page before that one.
A piece of my code:
The pushstate
var params = $('fme_layered_params').value.parseQuery();
// Object { cat="274", dir="desc"}
if (!params['dir'])
{
$('fme_layered_params').value += '&dir=' + 'desc';
}
if(window.location.href.indexOf('?') > -1){
var u = window.location.href.split("?");
var url = u[0]+"?"+$('fme_layered_params').value;
}else
var url = window.location.href+"?"+$('fme_layered_params').value;
window.history.pushState($('fme_layered_params').value, 'link', url);
The popstate
window.onpopstate = function(e) {
if(e.state!==null){
var state = e.state;
state = state.split("&");
// ["cat=272", "dir=desc"]
jQuery.each(state, function(i,v){
var info = v.split('=');
fme_layered_add_params(info[0], info[1], 1);
});
fme_layered_make_request();
}
};
What am i doing wrong?
Thanks in advance!
I solved this problem.
Every time i went back it also pushed the current page. So i went back and the same page was pushed in history resulting in endless reloading of that certain page.
TL;DR?
check, double check, triple check how your code 'walks' through the lines.

Laravel 3 - Passing variables across multiple views

I have two forms on two different views. I would like to post the form input to the second view, and then back to the first form upon posting the second form.
I have set up a test with a route that looks like this :
Route::get('/test1', function() {
return View::make('test1');
});
Route::post('/test2', function() {
$flash = Input::get();
return View::make('test2')->with('flash', $flash);
});
Route::post('/test1', function() {
return View::make('test1')->with('flash', $flash);
});
I am only able to pass $flash once. I'm misunderstanding why I cannot pass it again. I feel like I have to extract it again?
You need to add a form field in /test2 and resubmit the $flash data in order to pass it to /test1 via POST. It's a new request, the app will lose the $flash var otherwise.
A different approach could be to store $flash in a session with Session::put('flash', $flash); and accessing it in the next request.
The best method is to store your data in session. It will be available across multiple request . Using Input::flash() will only be available until the next request. See the Laravel docs for Input::flash() and Session

Laravel role based page access - Trying to get property of non-object - Auth::user()

Still very new to Laravel 3 and working through some issues.
I'm trying to set up a role based access to page. Users are currently able to log in and that part works well but I want to restrict access to certain pages based on the users role eg admin, editor etc.
So, I've created a filter as follows:
Route::filter('check_roles', function () {
$current_url = URI::current(); //get current url excluding the domain.
$current_page = URI::segment(2); //get current page which is stored in the second uri segment. Just a refresher: //the first uri segment is the controller, the second is the method,
//third and up are the parameters that you wish to pass in
$access = 0;
$counter = 1;
//excluded pages are the pages we don't want to execute this filter
//since they should always be accessible for a logged in user
$excluded_pages = array(
'base' => array('login', 'user/authenticate'),
1 => array('user/profile', 'dashboard','dashboard/index','articles','articles/index', 'articles/create', 'articles/preview', 'articles/edit', 'user/profile', 'user/logout'),
2 => array('articles/publish','user/create', 'user/edit'),
3 => array('user/delete')
);
if (!in_array($current_url, $excluded_pages['base']) ) { //if current page is not an excluded pages
if(Auth::user()->level < 4) {
do {
if (in_array($current_url, $excluded_pages[$counter])) {
$access=1;
}
$counter++;
} while ($counter < $user_level AND $counter < 4);
if ($access == 0) { //if user doesn't have access to the page that he's trying to access
//redirect the user to the homepage
return Redirect::to('dashboard')
->with('error', 'You don\'t have permission to access the following page: ' . $current_url);
}
}
}
This is based on tutorial I found https://gist.github.com/anchetaWern/4223764
My thoughts were depending on user access level which is 'level' in the user object I'd filter the pages etc.
However I'm getting an error 'Trying to get property of non-object' this relates to this code:
if(Auth::user()->level < 4) {
testing Auth::user()->level in a view confirms the user is logged in. Can anyone advise why this doesnt work in the routes.php as a filter?
Thank you
Problem solved - I was using the incorrect syntax in my script which I realised once I posted here.
if($user_level = Auth::user()->level < 4) {
Should be:
if(Auth::user()->level < 4) {
Filter works now. However I'm looking at ways to improve as not sure this is the most efficient way now!
Thanks

Typeahead with database as source

This snippet works and it correctly sets data from Table Product and column name as input for bootstrap typeahead extension for YII.
but, I have ended up writing a SELECT ALL from Table Product which is having large number of data.
Can we modify this so that a WHERE condition can be added to the DataProvider on user input event. Based on each alphabet entered, a new query could then be fired and only a subset of data retrieved?
<?php
$dataProvider = new CActiveDataProvider('Product');
$dataArray = $dataProvider->getData();
$myarray = array();
foreach ($dataArray as $data){
array_push($myarray, CHtml::encode($data->name));
}
$this->widget('bootstrap.widgets.TbTypeahead', array(
'name' => 'typeahead',
'options'=>array(
'name'=>'typeahead',
'source'=>$myarray,
'items'=>4,
'matcher'=>"js:function(item) {
return ~item.toLowerCase().indexOf(this.query.toLowerCase());
}",
),
'htmlOptions'=>array('class'=>'search-query span3', 'placeholder'=>"Search" ),
)); ?>
Once you start to supply a function to source, then you have the power to manipulate what happens, including how often you send requests.
minLength: 3, // <- custom option
source: function(query, process) {
var longEnough = query.length >= this.options.minLength;
// you can create custom variables (this.search) that a remembered across
// searches
if (longEnough && (! this.search || whateverRuleYouWantToLimitBy)) {
// remember the query so that you can compare it to the next one
this.search = query;
$.ajax({
url: '/ajaxsearch.php?value=' + query,
type: "GET",
success: process
});
}
}
I have some code that does something similar, and I cache the results returned by the Ajax code, and then I see if the new query string has the potential to change the results (e.g., if you limit by 4 results, but I only have 3 results, then a query that simply adds to the last query (search) has no need to hit the server).
Alternatively, you can kick off a timer that effectively waits for the user to stop typing to avoid the behavior of hitting the server for every key press. Technically, that results in slower feedback, but it's better for the server and mobile users. This is appropriate on sites that have a lot of traffic.

Instagram API: How to get all user media?

In general I need to get all user media.
User has more than 250 photos.
I do /users/1/media/recent/?access_token=...&count=250
But it returns only 20 photos.
Maybe instagram has a limit for getting media.
If it is, response has a pagination to solve it.
But there are only max ID photo. How to know the first (min) ID photo to paginate it then?
You're right, the Instagram API will only return 20 images per call. So you'll have to use the pagination feature.
If you're trying to use the API console. You'll want to first allow the API console to authenticate via your Instagram login. To do this you'll want to select OAUTH2 under the Authentication dropdown.
Once Authenticated, use the left hand side menu to select the users/{user-id}/media/recent endpoint. So for the sake of this post for {user-id} you can just replace it with self. This will then use your account to retrieve information.
At a bare minimum that is what's needed to do a GET for this endpoint. Once you send, you'll get some json returned to you. At the very top of the returned information after all the server info, you'll see a pagination portion with next_url and next_max_id.
next_max_id is what you'll use as a parameter for your query. Remember max_id is the id of the image that is the oldest of the 20 that was first returned. This will be used to return images earlier than this image.
You don't have to use the max_id if you don't want to. You can actually just grab the id of the image where you'd like to start querying more images from.
So from the returned data, copy the max_id into the parameter max_id. The request URL should look something like this https://api.instagram.com/v1/users/self/media/recent?max_id=XXXXXXXXXXX where XXXXXXXXXXX is the max_id. Hit send again and you should get the next 20 photos.
From there you'll also receive an updated max_id. You can then use that again to get the next set of 20 photos until eventually going through all of the user's photos.
What I've done in the project I'm working on is to load the first 20 photos returned from the initial recent media request. I then, assign the images with a data-id (-id can actually be whatever you'd like it to be). Then added a load more button on the bottom of the photo set.
When the button is clicked, I use jQuery to grab the last image and it's data-id attribute and use that to create a get call via ajax and append the results to the end of the photos already on the page. Instead of a button you could just replace it to have a infinite scrolling effect.
Hope that helps.
I've solved this issue with the optional parameter count set to -1.
It was a problem in Instagram Developer Console. max_id and min_id doesn't work there.
See http://instagram.com/developer/endpoints/ for information on pagination. You need to subsequentially step through the result pages, each time requesting the next part with the next_url that the result specifies in the pagination object.
In June 2016 Instagram made most of the functionality of their API available only to applications that have passed a review process. They still however provide JSON data through the web interface, and you can add the parameter __a=1 to a URL to only include the JSON data.
max=
while :;do
c=$(curl -s "https://www.instagram.com/username/?__a=1&max_id=$max")
jq -r '.user.media.nodes[]?|.display_src'<<<"$c"
max=$(jq -r .user.media.page_info.end_cursor<<<"$c")
jq -e .user.media.page_info.has_next_page<<<"$c">/dev/null||break
done
Edit: As mentioned in the comment by alnorth29, the max_id parameter is now ignored. Instagram also changed the format of the response, and you need to perform additional requests to get the full-size URLs of images in the new-style posts with multiple images per post. You can now do something like this to list the full-size URLs of images on the first page of results:
c=$(curl -s "https://www.instagram.com/username/?__a=1")
jq -r '.graphql.user.edge_owner_to_timeline_media.edges[]?|.node|select(.__typename!="GraphSidecar").display_url'<<<"$c"
jq -r '.graphql.user.edge_owner_to_timeline_media.edges[]?|.node|select(.__typename=="GraphSidecar")|.shortcode'<<<"$c"|while read l;do
curl -s "https://www.instagram.com/p/$l?__a=1"|jq -r '.graphql.shortcode_media|.edge_sidecar_to_children.edges[]?.node|.display_url'
done
To make a list of the shortcodes of each post made by the user whose profile is opened in the frontmost tab in Safari, I use a script like this:
sjs(){ osascript -e'{on run{a}','tell app"safari"to do javascript a in document 1',end} -- "$1";}
while :;do
sjs 'o="";a=document.querySelectorAll(".v1Nh3 a");for(i=0;e=a[i];i++){o+=e.href+"\n"};o'>>/tmp/a
sjs 'window.scrollBy(0,window.innerHeight)'
sleep 1
done
What I had to do is (in Javascript) is go through all pages by using a recursive function. It's dangerouse as instagram users could have thousands of pictures i a part from that (so your have to controle it) I use this code: (count parameter I think , doesn't do much)
instagramLoadDashboard = function(hash)
{
code = hash.split('=')[1];
$('#instagram-pictures .images-list .container').html('').addClass('loading');
ts = Math.round((new Date()).getTime() / 1000);
url = 'https://api.instagram.com/v1/users/self/media/recent?count=200&min_timestamp=0&max_timestamp='+ts+'&access_token='+code;
instagramLoadMediaPage(url, function(){
galleryHTML = instagramLoadGallery(instagramData);
//console.log(galleryHTML);
$('#instagram-pictures .images-list .container').html(galleryHTML).removeClass('loading');
initImages('#instagram-pictures');
IGStatus = 'loaded';
});
};
instagramLoadMediaPage = function (url, callback)
{
$.ajax({
url : url,
dataType : 'jsonp',
cache : false,
success: function(response){
console.log(response);
if(response.code == '400')
{
alert(response.error_message);
return false;
}
if(response.pagination.next_url !== undefined) {
instagramData = instagramData.concat(response.data);
return instagramLoadMediaPage(response.pagination.next_url,callback);
}
instagramData = instagramData.concat(response.data);
callback.apply();
}
});
};
instagramLoadGallery = function(images)
{
galleryHTML ='<ul>';
for(var i=0;i<images.length;i++)
{
galleryHTML += '<li><img src="'+images[i].images.thumbnail.url+'" width="120" id="instagram-'+images[i].id+' data-type="instagram" data-source="'+images[i].images.standard_resolution.url+'" class="image"/></li>';
}
galleryHTML +='</ul>';
return galleryHTML;
};
There some stuff related to print out a gallery of picture.
Use the best recursion function for getting all posts of users.
<?php
set_time_limit(0);
function getPost($url,$i)
{
static $posts=array();
$json=file_get_contents($url);
$data = json_decode($json);
$ins_links=array();
$page=$data->pagination;
$pagearray=json_decode(json_encode($page),true);
$pagecount=count($pagearray);
foreach( $data->data as $user_data )
{
$posts[$i++]=$user_data->link;
}
if($pagecount>0)
return getPost($page->next_url,$i);
else
return $posts;
}
$posts=getPost("https://api.instagram.com/v1/users/CLIENT-ACCOUNT-NUMBER/media/recent?client_id=CLIENT-ID&count=33",0);
print_r($posts);
?>
You can user pagination of Instagram PHP API: https://github.com/cosenary/Instagram-PHP-API/wiki/Using-Pagination
Something like that:
$Instagram = new MetzWeb\Instagram\Instagram(array(
"apiKey" => IG_APP_KEY,
"apiSecret" => IG_APP_SECRET,
"apiCallback" => IG_APP_CALLBACK
));
$Instagram->setSignedHeader(true);
$pictures = $Instagram->getUserMedia(123);
do {
foreach ($pictures->data as $picture_data):
echo '<img src="'.$picture_data->images->low_resolution->url.'">';
endforeach;
} while ($pictures = $instagram->pagination($pictures));
Use the next_url object to get the next 20 images.
In the JSON response there is an pagination array:
"pagination":{
"next_max_tag_id":"1411892342253728",
"deprecation_warning":"next_max_id and min_id are deprecated for this endpoint; use min_tag_id and max_tag_id instead",
"next_max_id":"1411892342253728",
"next_min_id":"1414849145899763",
"min_tag_id":"1414849145899763",
"next_url":"https:\/\/api.instagram.com\/v1\/tags\/lemonbarclub\/media\/recent?client_id=xxxxxxxxxxxxxxxxxx\u0026max_tag_id=1411892342253728"
}
This is the information on specific API call and the object next_url shows the URL to get the next 20 pictures so just take that URL and call it for the next 20 pictures.
For more information about the Instagram API check out this blogpost: Getting Friendly With Instagram’s API
Instagram developer console has provided the solution for it. https://www.instagram.com/developer/endpoints/
To use this in PHP, here is the code snippet,
/**
**
** Add this code snippet after your first curl call
** assume the response of the first call is stored in $userdata
** $access_token have your access token
*/
$maximumNumberOfPost = 33; // it can be 20, depends on your instagram application
$no_of_images = 50 // Enter the number of images you want
if ($no_of_images > $maximumNumberOfPost) {
$ImageArray = [];
$next_url = $userdata->pagination->next_url;
while ($no_of_images > $maximumNumberOfPost) {
$originalNumbersOfImage = $no_of_images;
$no_of_images = $no_of_images - $maximumNumberOfPost;
$next_url = str_replace("count=" . $originalNumbersOfImage, "count=" . $no_of_images, $next_url);
$chRepeat = curl_init();
curl_setopt_array($chRepeat, [
CURLOPT_URL => $next_url,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $access_token"
],
CURLOPT_RETURNTRANSFER => true
]);
$userRepeatdata = curl_exec($chRepeat);
curl_close($chRepeat);
if ($userRepeatdata) {
$userRepeatdata = json_decode($userRepeatdata);
$next_url = $userRepeatdata->pagination->next_url;
if (isset($userRepeatdata->data) && $userRepeatdata->data) {
$ImageArray = $userRepeatdata->data;
}
}
}
}