Paginator Class - php

everyone. I am working on a site with smarty templates using php and a mysql database.
This is a more specific question than my first one which asked how to pass methods to a class. I thought it would be easier to repackage the question than edit the old one.
I have written a paginator script for my image gallery which displays images on the page. If a user has selected a category then only images in a particular category are shown and the results are always paginated.
The script is shown below.
$page_num = (isset($_GET['page_num']))?$_GET['page_num']:1; //if $_GET['page_num'] is set then assign to var $page_num. Otherwise set $page_num = 1 for first page
$category = (isset($_GET['req1']))?$_GET['req1']:'null'; //if $_GET['req1'] is set assign to $category other set $category to null
$items_pp = 5;
$total = $db->num_images_gallery($category); //returns the number of records in total('null') or in a particular category('category_name')
$pages_required = ceil($total/$items_pp); //total records / records to display per page rounded up
if($page_num > $pages_required){//in case the current page number is greater that the pages required then set it to the amount of pages required
$page_num = $pages_required;
}
if($page_num < 1){//in case the current page num is set to less that one set it back to 1
$page_num = 1;
}
$limit = "LIMIT " .($page_num - 1)*$items_pp . "," . $items_pp . ""; //if 5 results pre page and we on page 3 then LIMIT 10,5 that is record 10,11,12,13 and 14
$result = $db->get_images_gallery($category,$limit);
$i = 0;
while($row = $result->fetch_assoc()){
$images[$i]['file'] =$row['file'];
$images[$i]['file_thumb'] = str_replace('.','_thumbnail.',$row['file']);//show the thumbnail version of the image on the page
$images[$i]['title'] = $row['title'];
$images[$i]['description'] = $row['description'];
$i++;
}
if(!empty($images)){
$smarty->assign('images',$images);}else{
$smarty->assign('message',"There are no images to display in the ".ucwords(str_replace('_',' ',$category))." category");}
if($total > 0 && $pages_required >= 1){//only display this navigation if there are images to display and more than one page
$smarty->assign('page_scroll',$page_num . ' of ' . $pages_required);
$page_scroll_first = "<a href='".$_SERVER['REDIRECT_URL'] . "?page_num=1"."' >FIRST</a> <a href='".$_SERVER['REDIRECT_URL'] . "?page_num=" . ($page_num-1)."' ><<PREVIOUS</a>";
$page_scroll_last = " <a href='".$_SERVER['REDIRECT_URL'] . "?page_num=". ($page_num+1) . "'>NEXT>></a> <a href='" . $_SERVER['REDIRECT_URL'] . "?page_num=".$pages_required."'>LAST</a>";
if($page_num == 1){$page_scroll_first = "FIRST <<PREVIOUS";}
if($page_num == $pages_required){$page_scroll_last = "NEXT>> LAST";}
$smarty->assign('page_scroll_first',$page_scroll_first);//just use if statements to set the values for page scroll first and page scroll last and then assign them here
$smarty->assign('page_scroll_last',$page_scroll_last);
$smarty->assign('page_num',$page_num);
}
The script calls on two methods from my database class:
$db->num_images_gallery which looks like this:
function num_images_gallery($cat='null'){
$query = ($cat == 'null')?
"SELECT COUNT(*) AS images FROM images
LEFT JOIN image_categories ON (images.image_categories_id = image_categories.id)
WHERE images.gallery='1' AND image_categories.gallery = '1'"//no images should be shown in a category which is not intended to be shown at all
:
"SELECT COUNT(*) AS images FROM images
LEFT JOIN image_categories ON (images.image_categories_id = image_categories.id)
WHERE category = '{$cat}'
AND images.gallery='1' AND image_categories.gallery = '1'";
$result = $this->connection->query('SELECT COUNT(*) AS images FROM (?)',$x);
$row = $result->fetch_assoc();
$row_count = $row['images'];
echo $row_count;
return $row_count;
}
and the method $db::get_images_gallery() which looks like this:
function get_images_gallery($category,$limit){
$query = ($category=='null')?
"SELECT `file`,title,images.description,sizes,images.gallery,category FROM images
LEFT JOIN image_categories ON (images.image_categories_id = image_categories.id) WHERE images.gallery='1' AND image_categories.gallery = '1' {$limit}"
:
"SELECT `file`,title,images.description,sizes,images.gallery,category FROM images
LEFT JOIN image_categories ON (images.image_categories_id = image_categories.id)
WHERE category = '{$category}' AND images.gallery='1' AND image_categories.gallery = '1' {$limit}";
$result = $this->connection->query($query);
return $result;
}
I now want to create a class called paginate and put this script in it so i can display my site products paginated.
The main problem is that i need to use different functions to get the num of prodducts in my product table and then return the paginated results. How do i turn the script above into a class where i can change the functions which are used. I almost got an answer on my previous question, but the question was not specific enough.
Thanks
andrew

There's a Smarty Add-On for pagination.
You can find it here: http://www.phpinsider.com/php/code/SmartyPaginate/
For a quick example, extracted from the linked page:
index.php
session_start();
require('Smarty.class.php');
require('SmartyPaginate.class.php');
$smarty =& new Smarty;
// required connect
SmartyPaginate::connect();
// set items per page
SmartyPaginate::setLimit(25);
// assign your db results to the template
$smarty->assign('results', get_db_results());
// assign {$paginate} var
SmartyPaginate::assign($smarty);
// display results
$smarty->display('index.tpl');
function get_db_results() {
// normally you would have an SQL query here,
// for this example we fabricate a 100 item array
// (emulating a table with 100 records)
// and slice out our pagination range
// (emulating a LIMIT X,Y MySQL clause)
$_data = range(1,100);
SmartyPaginate::setTotal(count($_data));
return array_slice($_data, SmartyPaginate::getCurrentIndex(),
SmartyPaginate::getLimit());
}
index.tpl
{* display pagination header *}
Items {$paginate.first}-{$paginate.last} out of {$paginate.total} displayed.
{* display results *}
{section name=res loop=$results}
{$results[res]}
{/section}
{* display pagination info *}
{paginate_prev} {paginate_middle} {paginate_next}
Regarding your question about mixing the DB class and the Paginator class, it's all ok:
Your DB class will handle fetching data from DB
The SmartyPaginate class will handle the pagination
And your index.php just make the calls to each one where appropriate to set things out.
The idea is to keep responsibilities isolated.
Your DB class won't handle pagination, nor will your pagination class contain DB code.
From your other question, I think you were trying to do something too much convoluted for the problem at hand.
I'd suggest you to move all code that is DB-related inside your DB handling class and outside your index.php
This, for example:
$limit = "LIMIT " .($page_num - 1)*$items_pp . "," . $items_pp . ""; //if 5 results pre page and we on page 3 then LIMIT 10,5 that is record 10,11,12,13 and 14
This is DB logic, it generates (part of) an SQL string, so move it around.
It depends on 2 parameters, so find a way to get them available.
In this case, I'd suggest just passing both as parameters.
Instead of:
$result = $db->get_images_gallery($category,$limit);
Use:
$result = $db->get_images_gallery($category,$no_items, $page);
Also, your pager navigation rule should be inside your paginator class..
if($total > 0 && $pages_required >= 1){//only display this navigation if there are images to display and more than one page
$smarty->assign('page_scroll',$page_num . ' of ' . $pages_required);
$page_scroll_first = "<a href='".$_SERVER['REDIRECT_URL'] . "?page_num=1"."' >FIRST</a> <a href='".$_SERVER['REDIRECT_URL'] . "?page_num=" . ($page_num-1)."' ><<PREVIOUS</a>";
$page_scroll_last = " <a href='".$_SERVER['REDIRECT_URL'] . "?page_num=". ($page_num+1) . "'>NEXT>></a> <a href='" . $_SERVER['REDIRECT_URL'] . "?page_num=".$pages_required."'>LAST</a>";
if($page_num == 1){$page_scroll_first = "FIRST <<PREVIOUS";}
if($page_num == $pages_required){$page_scroll_last = "NEXT>> LAST";}
$smarty->assign('page_scroll_first',$page_scroll_first);//just use if statements to set the values for page scroll first and page scroll last and then assign them here
$smarty->assign('page_scroll_last',$page_scroll_last);
$smarty->assign('page_num',$page_num);
}
In this case, I hope the Add-On will handle it automatically for you.
You could then move this whole block, which does all your logic for fetching and preparing images data to a function (inside your ImageGalery class if you have one)
$total = $db->num_images_gallery($category); //returns the number of records in total('null') or in a particular category('category_name')
$pages_required = ceil($total/$items_pp); //total records / records to display per page rounded up
if($page_num > $pages_required){//in case the current page number is greater that the pages required then set it to the amount of pages required
$page_num = $pages_required;
}
if($page_num < 1){//in case the current page num is set to less that one set it back to 1
$page_num = 1;
}
$limit = "LIMIT " .($page_num - 1)*$items_pp . "," . $items_pp . ""; //if 5 results pre page and we on page 3 then LIMIT 10,5 that is record 10,11,12,13 and 14
$result = $db->get_images_gallery($category,$limit);
$i = 0;
while($row = $result->fetch_assoc()){
$images[$i]['file'] =$row['file'];
$images[$i]['file_thumb'] = str_replace('.','_thumbnail.',$row['file']);//show the thumbnail version of the image on the page
$images[$i]['title'] = $row['title'];
$images[$i]['description'] = $row['description'];
$i++;
}
Finally, on your index.php, all you have to do is:
Validate the parameters you received
Call your ImageGalery class to fetch the galery data (pass the parameters it needs)
Call your Pagination class to do the pagination (setting up navigation links, etc)
Set the Smarty template variables you need
And display it.
There is still lots of room for improvement, but I hope those few steps will help get your Image Galery code more clear.

Related

Pagination, wrong amount of results on first page

I'm making news website, and I have mysql db with field "time" like 1533074400
Everything is ok if I just print results from query, but if I want to print only news older than today, I get in result only 2 instead of 5 news on first page. Remaining 3 are on the second page.
The problem is with query, if I receive let's say I have in my database 10 results and only 7 of them are past news, so when I filter them descending by
if ($today > news_date)
I get 2 news on first page (the remaining 3 are invisible future news, blocked by code above) and the rest 5 news on second page. So my question is, what to do to get it properly: 5 news on first page and remaining 2 on second page?
$results_per_page = 5;
if (!isset($_GET['page'])) {
$page = 1;}
else {
$page = $_GET['page'];
}
$this_page_first_result = ($page-1)*$results_per_page;
$sql='SELECT * FROM news ORDER BY time DESC LIMIT ' . $this_page_first_result . ',' . $results_per_page;
$result = mysqli_query($con, $sql);
$number_of_results = mysqli_num_rows($result);
$today = strtotime("now");
while($row = mysqli_fetch_array($result))
{
$news_date = $row[1];
if ($today > $news_date) {
echo HTML;
}
}
$number_of_pages = ceil($number_of_results/$results_per_page);
for ($page=1;$page<=$number_of_pages;$page++) {
echo '' . $page . ' ';
}
Try this:
$sql='SELECT * FROM news WHERE time < '.time().' ORDER BY time DESC LIMIT ' . $this_page_first_result . ',' . $results_per_page;
//Note: time() is an equivalent to strtotime("now")
If you're doing pagination in MySQL you should also do your filtering inside MySQL.
[Edit: Additional explanation] If you paginate in MySQL and then filter in PHP you'll have weird instances like this crop up because MySQL doesn't know where you're intending to actually start from. In this particular example if you have a lot of future-dated items you could actually end up with several blank pages of results pages before you start to see entries.
[Edit edit:] Also, if you do this, you'll no longer need the if check in your PHP loop as MySQL will have already done that.

How to index a table with order by?

Using a while loop I'm able to return my table in the order I want, but after implementing pagination the variable I've created (counter) resets itself on each page, frustratingly. Example code:
$sql = ('SELECT id,name,logo FROM mytable ORDER BY name DESC LIMIT 25');
$query = mysqli_query($db_conx,$sql);
$counter = 0;
while ($row = $query->fetch_assoc()) {
$counter++;
echo "$counter, $row['id'], $row['name']";
echo "<br />";
}
I've tried many things and can't get this to work. Obviously my logic is flawed. The loop returns the correct results, but the $counter variable breaks on each page, resetting itself indefinitely.
What I am trying to do is get $counter to increase by 25 (representing results for each page) for each of the pages created by the pagination loop. Example code:
for ($i=1; $i<=$total_pages; $i++) {
echo "<a href='page.php?page=".$i."'>&nbsp[".$i."]</a> ";
$GLOBALS["counter"]+=25;
};
Obviously this was not working, so I am stumped at what I should try next. If anyone has any ideas I would love to hear them, I have heard great things about the SO community.
You seem to display only the first 25 results at any time.
You need to initialize $counter to zero if it's the first page, to 26 if it's the second page, and so on :
$counter = 0;
if(isset($_GET['counter'])){
$counter = intval($_GET['counter']);
}
You need to modify your query to fetch a different set of results for each page :
$sql = 'SELECT id,name,logo FROM mytable ORDER BY name DESC LIMIT ' . mysqli_real_escape_string($db_conx, $counter . ',25');
$query = mysqli_query($db_conx,$sql);
Then I assume you display a link to the other paginated pages, you need to pass it the value of $counter :
Next

php & input checkbox store value of each loop

I have a page that will display the names of people from one data base that is joined with another that tracks when two names are linked. Every thing works fine. However; I cannot make the data or specifically the right user name ($id2u) and a string ($permissions) transfer from the input boxes when the user selects one. The onclick event should then show the data fields as per the permission string. The problem is the displayed info is always the last user in the DB that was ran through the loop. Am trying to get the info to carry forward on the onclick event when a check box is clicked so that their info is displayed.
This is he function to call the display of data.
function display1(type) {document.getElementById("pi").style.display = "none"
document.getElementById(type).style.display = ""}
This is the call to the DB that generated the input boxes for each person.
$result1 = mysql_query('SELECT * FROM level1 LEFT JOIN linkreq ON level1.idnumber = linkreq.idme ORDER BY LEAST(lname, fname) DESC');
for ($i = mysql_num_rows($result1) - 1; $i >= 0; $i--){
if (!mysql_data_seek($result1, $i)){echo "Cannot seek to row $i: " . mysql_error() . "\n";continue;}
if (!($row = mysql_fetch_assoc($result1))) {continue;}
if($_SESSION["idnumber"] == $row['id2u']){
if($row['returnack'] == 0){$id2u = $row['idme']; echo'<font color="#FF6600">*</font> '.$row['title'].' '.$row['fname'].' '.$row['mname'].' '.$row['lname'].' '.$row['suffex'].'<br />';}
else{
$permissions = $row['permissions'];
parse_str($permissions);
$permissions;
$id2u = $row['idme'].' ';
echo '<input type="checkbox" value = " .$id2u. " onClick="display1(\'pi\');"> '
.$row['title'].' '.$row['fname'].' '.$row['mname'].' '.$row['lname'].' '.$row['suffex'].'
<br />';}}}
This is the data that gets displayed onclick(pi) but displays last user not the selected one. Basically if the $id2u variable data would carry over from the check box this would work. As can manually force it by setting $id2u = xxxxxx; just before the query.
$result2 = mysql_query('SELECT * FROM pi WHERE idnumber = "'.$id2u.'"');
for ($i = mysql_num_rows($result2) - 1; $i >= 0; $i--){
if (!mysql_data_seek($result2, $i)){echo "Cannot seek to row $i: " . mysql_error() . "\n";continue;}
if (!($row = mysql_fetch_assoc($result2))) {continue;}
echo '<br>row #'.$row['idnumber'];
if($id2u == $row['idnumber']){echo'...// PRINTS OUT DATA //...}}
</span>
I know this should be in mySQLi but for now just need to get it to work will convert later.
Why don't you use a while() loop ?
$query = mysql_query("...");
while($row = mysql_fetch_array($query))
{
...
}
This always works fine for me :)

How do I get the first and last results from a query?

I am creating a pagination script and I need to get the first and last results in the database query so that I can determine what results appear when the user clicks a page to go to. This is the code that I have at the minute:
// my database connection is opened
// this gets all of the entries in the database
$q = mysql_query("SELECT * FROM my_table ORDER BY id ASC");
$count = mysql_num_rows($q);
// this is how many results I want to display
$max = 2;
// this determines how many pages there will be
$pages = round($count/$max,0);
// this is where I think my script goes wrong
// I want to get the last result of the first page
// or the first result of the previous page
// so the query can start where the last query left off
// I've tried a few different things to get this script to work
// but I think that I need to get the first or last result of the previous page
// but I don't know how to.
$get = $_GET['p'];
$pn = $_GET['pn'];
$pq = mysql_query("SELECT * FROM my_table ORDER BY id ASC LIMIT $max OFFSET $get");
// my query results appear
if(!$pn) {
$pn = 1;
}
echo "</table><br />
Page $pn of $pages<br />";
for($p = 1;$p<=$pages;$p++) {
echo "<a href='javascript:void(0);' onclick='nextPage($max, $p);' title='Page $p'>Page $p</a> ";
}
I think you have few problems there, but I try to tackle them for you. First, as comments say above, you are using code that it vulnerable to SQL injection. Take care of that - you might want to use PDO, which is as easy use as MySQL extension, and will save you from many trouble (like injection).
But to your code, lets go through it:
You should ask DB to get count of the rows, not using mysql function, it's far more effective, so use SELECT count(*) FROM mytable.
For $pages use ceil() as you want all rows to be printed, if you have $max 5 and have 11 rows, round will make $pages 2, where you actually want 3 (last page just contains that last 11th row)
in LIMIT you want to LIMIT row_count OFFSET offset. You can calculate offset from page number, so: $max = row_count but $offset = ($max * $page) - $max. In your code if $get is directly the page, it means you get $get'th row (Not sure though what happens in your JS nextpage. Bare in mind that not all use JavaScript.)
I have prepared simple example here which uses PDO, maybe that gives you idea how simple it's use PDO.
The selecting rows shows example how to put parameters in SQL, it would be perfectly safe in this case state, 'SELECT * FROM pseudorows LIMIT '.$start.','.$max by I wanted to make an example how easy it is (and then safe):
// DB config
$DB_NAME = 'test';
$DB_USER = 'test';
$DB_PASSWD = 'test';
// make connection
try {
$DB_CONN = new PDO("mysql:host=localhost;dbname=".$DB_NAME, $DB_USER, $DB_PASSWD);
$DB_CONN->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die($e);
}
// lets say user param 'p' is page, we cast it int, just to be safe
$page = (int) (isset($_GET['p'])?$_GET['p']:1);
// max rows in page
$max = 20;
// first select count of all rows in the table
$stmt = $DB_CONN->prepare('SELECT count(*) FROM pseudorows');
$stmt->execute();
if($value = $stmt->fetch()) {
// now we know how many pages we must print in pagination
// it's $value/$max = pages
$pages = ceil($value[0]/$max);
// now let's print this page results, we are on $page page
// we start from position max_rows_in_page * page_we_are_in - max_rows_in_page
// (as first page is 1 not 0, and rows in DB start from 0 when LIMITing)
$start = ($page * $max) - $max;
$stmt = $DB_CONN->prepare('SELECT * FROM pseudorows LIMIT :start,:max');
$stmt->bindParam(':start',$start,PDO::PARAM_INT);
$stmt->bindParam(':max', $max,PDO::PARAM_INT);
$stmt->execute();
// simply just print rows
echo '<table>';
while($row = $stmt->fetch()) {
echo '<tr><td>#'.$row['id'].'</td><td>'.$row['title'].'</td></tr>';
}
echo '</table>';
// let's show pagination
for($i=1;$i<=$pages;$i++) {
echo '[ '.$i.' ]';
}
}
mysql_fetch_array returns an associative array
Which means you can use reset and end to get the first and last results:
$pqa = mysql_fetch_array($pq);
$first = reset($pqa);
$last = end($pqa);
I don't see how you plan to use the actual results, just page numbers should be sufficient for pagination.
Still, hope it helps. And yes, upgrade to mysqli, so your code doesn't get obsolete.

Wanting to use one MySQL table to affect multiple pages

So right now, I've got a "gallery" system on my homepage of my site. Take a look:
<?php
$objConnect = mysql_connect("mydb.db","hello","mypass") or die(mysql_error());
$objDB = mysql_select_db("mydb");
$pic2 = "SELECT * FROM gallery";
if (!isset($_GET['Page'])) $_GET['Page']='0';
$pic1 = mysql_query($pic2);
$Num_Rows = mysql_num_rows($pic1);
$Per_Page = 16; // Per Page
$Page = $_GET["Page"];
if(!$_GET["Page"])
{$Page=1;}
$Prev_Page = $Page-1;
$Next_Page = $Page+1;
$Page_Start = (($Per_Page*$Page)-$Per_Page);
if($Num_Rows<=$Per_Page)
{$Num_Pages =1;}
else if(($Num_Rows % $Per_Page)==0)
{$Num_Pages =($Num_Rows/$Per_Page) ;}
else
{$Num_Pages =($Num_Rows/$Per_Page)+1;
$Num_Pages = (int)$Num_Pages;}
$pic2 .=" order by GalleryID ASC LIMIT $Page_Start , $Per_Page";
$pic1 = mysql_query($pic2);
$cell = 0;
$link2 = "SELECT * FROM gallery";
$link1 = mysql_query($link2);
$link = mysql_fetch_array($link1);
$alt2 = "SELECT * FROM gallery";
$alt1 = mysql_query($alt2);
$alt = mysql_fetch_array($alt1);
echo '<div id="tablediv"><table border="0" cellpadding="17" cellspacing="0" class="table"><tr>';
while($pic = mysql_fetch_array($pic1))
{if($cell % 4 == 0) {
echo '</tr><tr>';}
if($cell == 2) {
echo '<td>reserved cell, ignore this</td>';
} elseif ($cell == 3) {
echo '<td>reserved cell, ignore this</td>';
} else {
echo '
<td><div class="image"><img src="https://s3.amazonaws.com/images/' . $pic["pic"] . '" alt="' . $alt["alt"] . ' /></div></td>'; }
$cell++;
}
echo '</tr></table></div>';
?>
Anyhow... as you can see, with this system, whenever I insert a new record, it automatically updates my gallery. Now my question is how can I make it so when I insert a new record, it doesn't just affect my homepage gallery, it affects the galleries on the other subsections of my website as well.
Say my site is called site.com . I also have a site.com/nature . My site.com/nature is only for nature photos, but I don't want to manually update /nature by creating a whole new set of table and update that manually. Rather I'd rather take an easier route, so in my gallery table, I can specify whether or not I want it in /nature.
I presume I would need another column (obviously) for specifying what other folders do I want my record to appear in, or maybe some conditional statements to determine which subfolder should my record also appear in and not just my homepage. Unfortunately, I'm a nooblet, so I'm asking if fellow stackers can help me with this. Thanks!
I'm not sure of the exact schema you're using but if your gallery is sub divided into categories like nature, animation. its best to have one extra column ("category") probably a varchar or string (sorry not familiar with exact types in mysql) that specifies the category, so in the nature category you would go like:
select *
from gallery g
where g.category = 'nature'
So if you have another category like pokemon you would go:
select *
from gallery g
where g.category = 'pokemon'
This way you can have many different categories for you site.
but in the main page where you want all the pictures just have:
select *
from gallery g
Also, its best that instead of returning * you return only the fields you actually need. And possible use select DISTINCT filndName so you're not getting repeats the same tuple (entry) when grabbing it from sql
There are a couple of different ways you can go about this. Since you don't want to create another table, one way you can approach it is to use a column of the SET data type. This would allow you to create a set of all the subsections you have (up to 64) with one gallery item being possibly in multiple subsections.
The possible problems are the limit of 64 subsections, of course, and the fact that adding a new subsection requires an ALTER statement which, depending on how you go about adding a subsection, could cause permission issues among other things.
In my opinion, a better way is to add two new tables. One table will be a subsection table which only needs an id and a subsection path ('nature'). The other table will be a gallery-subsection table that has the gallery id and subsection id so that one gallery item can be in multiple subsections. This is not only easier to add a new subsection, but it also allows for many more than 64 subsections.

Categories