Pagination issue with WP_List_table in WordPress - php

I'm developing a plugin for WordPress. I use WP_List_Table which output rows from database nicely.
The issue I have is the pagination. If I have let's say 200 rows in the database and I set it to only display 50 rows per page, this works and I do get 1 of 4 pages.
On page 1 It displays ID 1 to ID 50. But when I click on page 2 it displays ID 2 to ID 51. So it only jumps 1 row and not 50. Where's the issue?
This will handle the data:
function prepare_items() {
global $wpdb;
/* Select project for user */
$table_name2 = $wpdb->prefix . 'project_name';
$current_user = wp_get_current_user();
$sql2 = "SELECT * FROM " . $wpdb->prefix . "project_name WHERE alloweduser = " . $current_user->ID;
$results2 = $wpdb->get_results($sql2) or die(mysql_error());
foreach( $results2 as $result2 ) {
$table_name = $wpdb->prefix . "project_name_" . $result2->projectname;
/* Define how many rows to show per page */
$per_page = 50;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
// here we configure table headers, defined in our methods
$this->_column_headers = array($columns, $hidden, $sortable);
// [OPTIONAL] process bulk action if any
$this->process_bulk_action();
// will be used in pagination settings
$total_items = $wpdb->get_var("SELECT COUNT(cid) FROM $table_name");
// prepare query params, as usual current page, order by and order direction
$paged = isset($_REQUEST['paged']) ? max(0, intval($_REQUEST['paged']) - 0) : 0;
$orderby = (isset($_REQUEST['orderby']) && in_array($_REQUEST['orderby'], array_keys($this->get_sortable_columns()))) ? $_REQUEST['orderby'] : 'cid';
$order = (isset($_REQUEST['order']) && in_array($_REQUEST['order'], array('ASC', 'DESC'))) ? $_REQUEST['order'] : 'ASC';
// Define $items array
// notice that last argument is ARRAY_A, so we will retrieve array
$this->items = $wpdb->get_results($wpdb->prepare("SELECT * FROM $table_name ORDER BY $orderby $order LIMIT %d OFFSET %d", $per_page, $paged), ARRAY_A);
// configure pagination
$this->set_pagination_args(array(
'total_items' => $total_items, // total items defined above
'per_page' => $per_page, // per page constant defined at top of method
'total_pages' => ceil($total_items / $per_page) // calculate pages count
));
}
}
}
This will output the data:
function custom_table_example_submissions_page_handler() {
global $wpdb;
$table = new Custom_Table_Example_List_Table();
$table->prepare_items();
$message = '';
if ('delete' === $table->current_action()) {
$message = '<div class="updated below-h2" id="message"><p>' . sprintf(__('Deleted %d submission(s).', 'custom_table_example'), count($_REQUEST['id'])) . '</p></div>';
}
?>
<div class="wrap">
<div class="icon32 icon32-posts-post" id="icon-edit">
<br></div>
<h2><?php _e('Submissions', 'custom_table_example')?>
</h2>
<?php echo $message; ?>
<form id="submissions-table" method="GET">
<input type="hidden" name="page" value="<?php echo $_REQUEST['page']; ?>"/>
<?php //$table->display(array('contributorname', 'email')); ?>
<?php $table->display(); ?>
</form>
</div>
<?php
}
Update:
get_columns:
function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
'name' => __('Name', 'custom_table_example'),
'upload' => __('Upload', 'custom_table_example'),
'upload2' => __('Upload', 'custom_table_example'),
'upload3' => __('Upload', 'custom_table_example'),
'upload4' => __('Upload', 'custom_table_example'),
'rate' => __('Rate', 'custom_table_example'),
);
return $columns;
}
$this->items is retrieved from:
https://core.trac.wordpress.org/browser/tags/4.0.1/src//wp-admin/includes/class-wp-list-table.php#L0
It's called with:
if (!class_exists('WP_List_Table')) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
}

I managed to solve it and answer my own question.
The issue was here:
$paged = isset($_REQUEST['paged']) ? max(0, intval($_REQUEST['paged']) - 0) : 0;
Since I want to output 50 rows per page I simply added -1 to $_REQUEST['paged'] and added * 50 which means that on page 1 row 1-50 is displayed and on page 2 rows 51-100 is displayed since 2*50 is 100 and so on.
Correct solutions is this:
$paged = isset($_REQUEST['paged']) ? max(0, intval($_REQUEST['paged'] -1) * 50) : 0;

I have same issue in my project so solution is here
$per_page = 5;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action();
$paged = isset($_REQUEST['paged']) ? max(0, intval($_REQUEST['paged'] -1) * $per_page) : 0;
I hope Now your problem will be solve

$found_data = array_slice($this->table_data(),(($current_page-1)*$per_page),$per_page);
$this->items = $found_data;
Use above mention code in your prepare_items(). It devides your list of data into the chunks.

Related

Woocommerce - Implementation of query args in the reviews counting graph only for the currently selected language

I have a code that displays reviews graph of the entire website and divides them into sections according to ratings. The problem is that this code calculates the total reviews of the whole website. I would need to filter the total reviews count according to the currently chosen language. I use WPML. Any advice?
Code:
function display_all_product_review_histogram($minimum_rating, $maximum_rating){
$all_product_review_average_rating = get_all_product_review_average_rating($minimum_rating, $maximum_rating);
$total_ratings = $all_product_review_average_rating[0]["total_ratings"];
$get_all_product_review_counts_by_ratings = get_all_product_review_counts_by_ratings($minimum_rating, $maximum_rating);
if($get_all_product_review_counts_by_ratings){
$output = '';
$sum = 0;
$total = 0;
$raw_percentages_array = array();
$percentages_array = array();
//When working with rounded percentages, we must make sure the total percentages add up to 100%.
//Creating array of rating values and its percentage
foreach ($get_all_product_review_counts_by_ratings as $key => $rating) {
$percentage = round($rating["amount"] / $total_ratings, 2) * 100;
$raw_percentages_array[] = array("value" => $rating["value"], "percent_of_total" => $percentage, 'amount'=> $rating["amount"]);
}
//Counting the total of our percents
foreach($raw_percentages_array as $key => $percent) {
$total += $percent[ "percent_of_total" ];
}
//Creating an array that will have the actual percentages after the rounding has been applied to it.
//This will help to see if we have 100% or we are not aligned
foreach($raw_percentages_array as $key => $percent){
$percentages_array[$percent["value"]] = round(($percent["percent_of_total"]/$total) * 100, 0);
}
$sum = array_sum($percentages_array); //Again counting the total of our new percents to see if it adds up to 100%
if($sum != 100){ //If we do not have 100%, then we will alter the highest percentage value so that we get a total of 100%
$highest_percentage_key = array_keys($percentages_array, max($percentages_array)); //Getting key of the highest percentage value
$percentages_array[$highest_percentage_key[0]] = 100 - ($sum - max($percentages_array)); //Alterning the percentage
}
//Now we are ready to create the output that will give us 100% in total
$r_count = 0;
$output .= "<div class='product-review-histogram'>";
foreach ($percentages_array as $key => $percentage) {
$output .= "<div class='histogram-row star-rating-". $key ."'>";
$output .= "<div class='histogram-col-1'>". $key ." star</div>";
$output .= "<div class='histogram-col-2'><div class='histogram-meter-bar'><div class='histogram-bar-temperature' style='width: ". $percentage ."%'></div></div></div>";
$output .= "<div class='histogram-col-3'>". $raw_percentages_array[$r_count]['amount'] ."</div>";
$output .= "</div>";
$r_count++;
}
$output .= "</div>";
return $output;
}else{
return;
}
}
UPDATE - sharing code of get_all_product_review_average_rating function:
function get_all_product_review_average_rating($minimum_rating, $maximum_rating){
$get_all_product_review_counts_by_ratings = get_all_product_review_counts_by_ratings($minimum_rating, $maximum_rating);
if($get_all_product_review_counts_by_ratings){ //If we have reviews
$average_rating_results = array();
$total_ratings = 0;
$total_rating_value = 0;
foreach ($get_all_product_review_counts_by_ratings as $key => $rating) {
$total_ratings = $total_ratings + $rating["amount"];
$current_rating_value = $rating["amount"] * $rating["value"];
$total_rating_value = $total_rating_value + $current_rating_value;
}
$average_rating = number_format($total_rating_value / $total_ratings, 1); //Rounding value to one decimal place
$average_rating_results[] = array(
"total_ratings" => $total_ratings,
"average_rating" => $average_rating
);
return $average_rating_results;
}else{
return;
}
}
UPDATE2 - sharing more of the functions related to the graph
function get_all_product_review_ratings(){
global $wpdb;
if ( false === ( $review_ratings = get_transient( 'all_product_review_ratings' ))){ //Checking if we have previously cached query results in order to save resources and increase speed
$review_ratings = $wpdb->get_results("
SELECT meta_value
FROM {$wpdb->prefix}commentmeta as commentmeta
JOIN {$wpdb->prefix}comments as comments ON comments.comment_id = commentmeta.comment_id
WHERE commentmeta.meta_key = 'rating' AND comments.comment_approved = 1
ORDER BY commentmeta.meta_value
", ARRAY_A);
$expiration = 60 * 5; //Expiring query results after 5 minutes
set_transient( 'all_product_review_ratings', $review_ratings, $expiration ); //Temporarily storing cached data in the database by giving it a custom name and a timeframe after which it will expire and be deleted
return $review_ratings;
}else{
return $review_ratings;
}
}
WHAT DO WE NEED TO CHANGE TO GET THIS IS PROBABLY THIS PART OF THE CODE:
$review_ratings = $wpdb->get_results("
SELECT meta_value
FROM {$wpdb->prefix}commentmeta as commentmeta
JOIN {$wpdb->prefix}comments as comments ON comments.comment_id = commentmeta.comment_id
WHERE commentmeta.meta_key = 'rating' AND comments.comment_approved = 1
ORDER BY commentmeta.meta_value
", ARRAY_A);
UPDATE 3:
function get_all_product_review_counts_by_ratings($minimum_rating, $maximum_rating){
$all_product_review_ratings = get_all_product_review_ratings();
if($all_product_review_ratings){ //If we have reviews
$all_product_review_ratings_one_dimensional_array = array_map("current", $all_product_review_ratings); //Converting two dimensional array to one dimensional array
$rating_counts = array_count_values($all_product_review_ratings_one_dimensional_array); //Creating array that consists of rating counts
$ratings = array();
while($maximum_rating >= $minimum_rating){
if(array_key_exists($maximum_rating, $rating_counts)){
$star_count = $rating_counts[$maximum_rating];
}else{
$star_count = 0;
}
//Creating array that contains information about
$ratings[] = array(
"value" => $maximum_rating,
"amount" => $star_count
);
$maximum_rating--;
}
return $ratings;
}else{
return;
}
}
function get_all_product_review_ratings() {
global $wpdb;
if (false === ( $review_ratings = get_transient('all_product_review_ratings'))) { //Checking if we have previously cached query results in order to save resources and increase speed
$review_ratings = array();
$args = array(
'status' => 'approve',
'type' => 'review',
'paged' => 0,
'meta_query' => array(
array(
'key' => 'verified',
'value' => 1
)
));
// The Query for getting reviews - WPML respected
$comments_query = new WP_Comment_Query;
$comments = $comments_query->query($args);
foreach ($comments as $comment) {
$review_ratings[] = get_comment_meta($comment->comment_ID, 'rating', 1);
}
$expiration = 60 * 5; //Expiring query results after 5 minutes
set_transient('all_product_review_ratings', $review_ratings, $expiration); //Temporarily storing cached data in the database by giving it a custom name and a timeframe after which it will expire and be deleted
return $review_ratings;
} else {
return $review_ratings;
}
}

Invalid argument supplied for foreach() inside shopping cart ,

I am trying to get more than 1 item in my shopping cart array but I run into an issue
This is my database after inserting 1 item with post method
Items is defined in db as text
After trying to insert second product the items result gets overwritten.
My developers tools output after clicking add to cart
ADD
I am using add to cart button from my modal >https://pastebin.com/HKSRTG4L that is submitting via ajax and parsed to add-cart.php
Where I have add to cart function : https://pastebin.com/guv0rB6x
This is my code from cart.php :
<?php
include_once $_SERVER['DOCUMENT_ROOT']."/EcomApp/konfiguracija.php";
require_once $_SERVER['DOCUMENT_ROOT'].'/EcomApp/config.php';
include 'include/head.php';
include 'include/izbornik.php';
if($cart_id != ''){
$cartQ = $veza->prepare("SELECT * FROM cart WHERE id = '$cart_id';");
$cartQ->execute();
$result= $cartQ->fetch(PDO::FETCH_ASSOC);
$items = json_decode($result['items'],true);var_dump($items) ;
$i = 1;
$sub_total = 0;
$item_count = 0;
}
?>
<div class="col-md-12">
<div class="row">
<h2 class ="text-center">Your Shopping Cart </h2><hr>
<?php if($cart_id =='') :?>
<div class="bg-danger">
<p class="text-center text-danger">
Your shopping cart is empty!
</p>
</div>
<?php else: ?>
<table class="table" >
<thead><th>#</th><th>Item</th><th>Price</th><th>Quantity</th><th>Size</th><th>Sub Total</th></thead>
<tbody>
<?php
foreach ($items as $item){
$product_id =$item['id'];
$productQ = $veza ->prepare("SELECT * FROM products WHERE id = '$product_id'");
$productQ ->execute();
$product= $productQ->fetch(PDO::FETCH_ASSOC);
$sArray = explode (',',$product['sizes']);
foreach($sArray as $sizeString){
$s = explode(':',$sizeString);
if($s[0] ==$item['size']){
$available = $s[1];
}
}
?>
<tr>
<td><?=$i;?></td>
<td><?=$product['title'];?></td>
<td><?=$product['price'];?></td>
<td><?=$item['quantity'];?></td>
<td><?=$item['size'];?></td>
<td><?=$item['quantity'] * $product['price'];?></td>
</t>
<?php } ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php include 'include/footer.php';?>
I am 93% sure the problem is with array merge since currently the insert is overwriting the row with new results and not adding to the array.
<?php
require_once $_SERVER['DOCUMENT_ROOT'].'/EcomApp/konfiguracija.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/EcomApp/config.php';
$product_id = sanitize($_POST['product_id']);
$size = sanitize($_POST['size']);
$available = sanitize($_POST['available']);
$quantity = sanitize($_POST['quantity']);
$item = array();
$item[]= array(
'id' => $product_id,
'size' => $size,
'quantity' => $quantity,
);
$domain = ($_SERVER['HTTP_HOST'] != 'localhost')?'.'.$_SERVER['HTTP_HOST']:false;
$query = $veza->prepare("SELECT * FROM products WHERE id = '$product_id'");
$query ->execute();
$product = $query->fetch(PDO::FETCH_ASSOC);
$_SESSION['success_launch'] = $product['title']. 'was added to your cart.';
//check does cookie cart exist
if($cart_id != ''){
$cartQ= $veza->prepare("SELECT * FROM cart WHERE id = '$cart_id'");
$cart = $cartQ->fetch(PDO::FETCH_ASSOC);
$previous_items = json_decode($cart['items'],true);
$item_match = 0;
$new_items = array();
foreach ($prevous_items as $pitem){
if($item[0]['id']==$pitem['id'] && $item[0]['size'] == $pitem['size']){
$pitem ['quantity']= $pitem['quantity']+$item[0]['quantity'];
if ($pitem['quantity']>$available){
$pitem['quantity'] = $available;
}
$item_match = 1;
}
$new_items[] = $pitem;
}
if($item_match != 1){
$new_items = array_merge($item,(array)$previous_items);
}
$items_json = json_encode($new_items);
$cart_expire = date("Y-m-d H:i:s", strtotime("+30 days"));
$something=$veza->prepare("UPDATE cart SET items = '$items_json',expire_date= '$cart_expire'WHERE id ='$cart_id'");
$something ->execute();
setcookie(CART_COOKIE,'',1,'/',$domain,false);
setcookie(CART_COOKIE,$cart_id,CART_COOKIE_EXPIRE,'/',$domain,false);
}else {
INSERT
//add cart inside database
$items_json = json_encode($item);
$cart_expire = date("Y-m-d H:i:s",strtotime("+30 days"));
$smth=$veza->prepare("INSERT INTO cart (items,expire_date) VALUES ('$items_json','$cart_expire')");
$smth->execute();
$cart_id = $veza->lastInsertId();
setcookie(CART_COOKIE,$cart_id,CART_COOKIE_EXPIRE,'/',$domain,false);
}
var_dump($cart_id);
?>
add to cart function : https://pastebin.com/guv0rB6x
function add_to_cart(){
jQuery('#modal_errors').html("");
var size = jQuery('#size').val();
var quantity = jQuery('#quantity').val();
var available = jQuery('#available').val();
var error = '';
var data = jQuery("#add_product_form").serialize();
if(size == '' || quantity == '' || quantity == 0){
error += '<p class= "bg-danger text-center">You must choose a size and quantity</p>';
jQuery('#modal_errors').html(error);
return;
}else if (quantity>available){
error += '<p class= "bg-danger text-center">There are only '+available+' available.</p>';
jQuery('#modal_errors').html(error);
return;
}else{
jQuery.ajax({
url: '/EcomApp/admin/parsers/add_cart.php',
method : 'post',
data : data,
success : function(){
location.reload();
},
error : function(){alert("Something went wrong");}
});
}
}
konfiguracija.php there is a (Undefined offset: 1) on line 65
$user_data['last'] = $fn1;
but I think it is not directly connected to the functionality
try{
$veza = new PDO("mysql:host=" . $host . ";dbname=" . $dbname,$dbuser,$dbpass);
$veza->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$veza->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8';");
$veza->exec("SET NAMES 'utf8';");
}catch(PDOException $e){
switch($e->getCode()){
case 1049:
header("location: " . $eone . "error/wrongDBname.html");
exit;
break;
default:
header("location: " . $eone . "error/error.php?code=" . $e->getCode());
exit;
break;
}
}
require_once $_SERVER['DOCUMENT_ROOT'].'/EcomApp/config.php';
require_once BASEURL.'helpers/helpers.php';
session_start();
//
$cart_id = '';
if(isset($_COOKIE[CART_COOKIE])){
$cart_id = sanitize($_COOKIE[CART_COOKIE]);
}
if(isset($_SESSION['SDUser'])){
$user_id =$_SESSION['SDUser'];
$query = $veza->prepare("SELECT* FROM korisnik WHERE id ='$user_id'");
$query->execute();
$user_data = $query->fetch(PDO::FETCH_ASSOC);
$fn = explode(' ', $user_data['full_name']);
$user_data['first'] = $fn[0];
$user_data['last'] = $fn[1];
// print_r($user_data);
}
if(isset($_SESSION['success_launch'])){
echo '<h1><p class="text-success">'.$_SESSION['success_launch'].'</p></h1>';
unset($_SESSION['success_launch']);
}
if(isset($_SESSION['error_launch'])){
echo '<div class="success"><p class="text-success">'.$_SESSION['error_launch'].'</p></div>';
unset($_SESSION['error_launch']);
}
In add_cart.php:
$previous_items = json_decode($cart['items'],true);
$item_match = 0;
$new_items = array();
foreach ($prevous_items as $pitem){
Notice that $previous_items and $prevous_items are different.
That's why you're getting:
Notice: Undefined variable: prevous_items in
/opt/lampp/htdocs/EcomApp/admin/parsers/add_cart.php on line 29
You didn't take my previous advice to make your life easier.
Apparently working code is not enough. Always validate and fail fast ( http://www.practical-programming.org/ppl/docs/articles/fail_fast_principle/fail_fast_principle.html ):
if(!$cart_id){
$cartQ = $veza->prepare("SELECT * FROM cart WHERE id = ?");
$cartQ->execute([(int)$cart_id]);
$result= $cartQ->fetch(PDO::FETCH_ASSOC);
if (!isset($result['items'])) {
throw new \RuntimeException("The items were not found.");
}
$items = json_decode($result['items'],true);
if (!$items) {
throw new \RuntimeException("Invalid items format.");
}
$i = 1;
$sub_total = 0;
$item_count = 0;
}
As said, do something like this:
<?php
$validKeys = ['product_id', 'size', 'available', 'quantity'];
foreach ($validKeys as $key) {
if (!isset($_POST[$key])) {
throw new RuntimeException("Parameter $key is missing.");
}
}
require_once $_SERVER['DOCUMENT_ROOT'].'/EcomApp/konfiguracija.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/EcomApp/config.php';
$product_id = sanitize($_POST['product_id']);
$size = sanitize($_POST['size']);
$available = sanitize($_POST['available']);
$quantity = sanitize($_POST['quantity']);
Most probably you missed something in the client side (javascript or HTML form), for instance, you might have misspelled "product_id".
ADDED:
https://pastebin.com/guv0rB6x
function add_to_cart(){
jQuery('#modal_errors').html("");
var size = jQuery('#size').val();
var quantity = jQuery('#quantity').val();
var available = jQuery('#available').val();
var error = '';
var data = jQuery("#add_product_form").serialize();
if(size == '' || quantity == '' || quantity == 0){
error += '<p class= "bg-danger text-center">You must choose a size and quantity</p>';
jQuery('#modal_errors').html(error);
return;
}else if (quantity>available){
error += '<p class= "bg-danger text-center">There are only '+available+' available.</p>';
jQuery('#modal_errors').html(error);
return;
}else{
jQuery.ajax({
url: '/EcomApp/admin/parsers/add_cart.php',
method : 'post',
data : data,
success : function(){
location.reload();
},
error : function(){alert("Something went wrong");}
});
}
}
You're not checking if the "product_id" field is empty.
ADDED:
Replace:
$previous_items = json_decode($cart['items'],true);
$item_match = 0;
$new_items = array();
foreach ($prevous_items as $pitem){
if($item[0]['id']==$pitem['id'] && $item[0]['size'] == $pitem['size']){
$pitem ['quantity']= $pitem['quantity']+$item[0]['quantity'];
if ($pitem['quantity']>$available){
$pitem['quantity'] = $available;
}
$item_match = 1;
}
$new_items[] = $pitem;
}
if($item_match != 1){
$new_items = array_merge($item,(array)$previous_items);
}
With something like this:
$previous_items = json_decode($cart['items'], true);
if (!$previous_items) {
$previous_items = array();
}
$items_by_unique = array();
foreach (array_merge($item, $previous_items) as $item) {
if (!isset($item['id'], $item['size'], $item['quantity'])) {
throw new RuntimeException('Found a invalid invalid data: ' . json_encode($item));
}
$unique = $item['id'] . '-' . $item['size'];
if (isset($items_by_unique[$unique])) {
$old_item = $items_by_unique[$unique];
$item['quantity'] = $old_item['quantity'];
}
if ($item['quantity'] > $available) {
$item['quantity'] = $available;
}
$items_by_unique[$unique] = $item;
}
$items_json = json_encode(array_values($items_by_unique));
The code has lots of issues that make very hard to find the mistakes and it's very hard to answer a question which is really several questions which are always increasing, as if we're making a debugging service for free... We could be discussing here for several hours and days, like a job, until everything is corrected. In that case, it's going to be very hard to help and take care of our personal lives and jobs.
Anyway, I'm going to enumerate some issues that make the code very hard to debug.
Most of the returned values are not validated, assuming that they're what's expected, instead failing immediately when there's an error. That means that you may only notice there's a mistake a lot later and hunt for the its source, which might be in a different file.
For instance, when you do something like this:
$query = $veza->prepare("SELECT * FROM products WHERE id = '$product_id'");
$query->execute();
$product = $query->fetch(PDO::FETCH_ASSOC);
You should always check if $product is at least not empty:
if (!$product) {
throw new RuntimeException("The product #$product_id" was not found.");
}
Also, putting variables inside SQL statements can be very dangerous. If you're using prepared statements you should do something like this:
$query = $veza->prepare("SELECT * FROM products WHERE id = ? LIMIT 1");
$query->execute([$product_id]);
The code does not take advantage of reusability. You can implement classes or functions and test them independently.
For instance, you could have done something like this:
<?php
/**
* Add a new item ...
*
* If it's being requested more items then available ...
*
* #param array $newItem The new item that should be added.
* #param array $items The list of items where the item should be added.
* #param integer $available Available items.
* #return void
*/
function add_item(array $newItem, array &$items, int $available=MAX_INT_MAX)
{
throw_error_if_invalid_item($newItem);
$unique = $newItem['id'] . '-' . $newItem['size'];
if (isset($items_by_unique[$unique])) {
add_item_quantity(
$items_by_unique[$unique],
$item['quantity'],
$available
);
} else {
$items[] = $newItem;
}
}
/**
* Add quantity ...
*
* #param array $item ...
* #param integer $moreQuantity ...
* #param integer $available ...
* #return void
*/
function add_item_quantity(array &$item, int $moreQuantity, int $available=MAX_INT_MAX)
{
$item['quantity'] += $moreQuantity;
if ($item['quantity'] > $available) {
$item['quantity'] = $available;
}
}
/**
* ...
*
* #param array $item ...
* #return void
*/
function throw_error_if_invalid_item(array $item)
{
if (!isset($item['id'], $item['size'], $item['quantity'])) {
throw new RuntimeException(
'Found a invalid invalid item data: ' . json_encode($item)
);
}
}
Then you can make individual test in isolated files:
<?php
$item = array(
'id' => 10,
'size' => 'm',
'quantity' => 2,
);
add_item_quantity($item, 3, 10);
if ($item['quantity'] != 5) {
echo "wrong! quantity = {$item['quantity']}\n";
} else {
echo "correct!\n";
}
If you put your classes or functions in dedicated files, you can reuse them as a library making the code easy to read, testable and secure:
<?php
throw_error_if_invalid_item($_POST);
$newItem = create_item($_POST);
$items = fetch_cart_items($cart_id);
add_item($newItem, $items, 10);
save_cart_items($items);
show_json_items($json);
With classes it would be much easier (eg: you wouldn't have to worry about missing array keys) and you could use PHPUnit to test all the functions and methods automatically. Check how I did that for a simple project:
https://github.com/pedroac/nonce4php
You can also make any error or warning halt the script:
<?php
set_error_handler(
function (int $errno , string $errstr, string $errfile, int $errline) {
http_response_code(500);
echo "ERROR #$errono:$errfile:$errline: $errstr";
die();
}
);
set_exception_handler(
function (Throwable $exception) {
http_response_code(500);
echo $exception->getMessage();
die();
}
);
If you follow the good practices, it will be much easier to found mistakes and let others help you.
I fixed this issue by casting the second argument of array_merge to an array:
$new_items = array_merge($item, (array)$previous_items);

Implode data from an array in an array

Goal : Get favorited posts IDs from authors ID.
Authors ID :
$currentid = $current_user->ID;
$fav_author_list = bp_follow_get_following( array( 'user_id' => $currentid ) );
echo implode(' ', $fav_author_list);
Result : 1, 45, 9
Favorited Posts ID from one specific Author ID (1) :
$authorID = "1";
$ok = get_user_favorites($authorID, $site_id);
echo implode(' ', $ok);
Result : 845, 895
I want Favorited Posts ID from multiple Authors ID (1, 45, 9) : NOT WORKING
$ok = get_user_favorites($fav_author_list, $site_id);
echo implode(' ', $ok);
$fav_author_list should be an unique value and this is my problem because I want get_user_favorites from multiple values
get_user_favorites function :
function get_user_favorites($user_id = null, $site_id = null, $filters = null)
{
global $blog_id;
$site_id = ( is_multisite() && is_null($site_id) ) ? $blog_id : $site_id;
if ( !is_multisite() ) $site_id = 1;
$favorites = new UserFavorites($user_id, $site_id, $links = false, $filters);
return $favorites->getFavoritesArray();
}
EDIT :
$currentid = $current_user->ID;
$fav_author_list = bp_follow_get_following( array( 'user_id' => $currentid ) );
$ok = get_user_favorites($fav_author_list[0]);
foreach ($ok as $name => $age) {
echo $name = $age;
}
I succeed to get some results from $fav_author_list[0] and $fav_author_list[1] etc, what I want is to get ALL results in same time

Codeigniter Pagination From Database limit, offset

I started a web application in CI 3.0, everything working smooth, I got my pagination working, but there is a problem wich I cannot figure out...
For example, I have the following URL: localhost/statistics/api_based
When navigate, $query results are displayed, links are generated, OK.
But, when I navigate to page 2 for example, the URL will become:
localhost/statistics/dll_based/index/2 and page 3 will become localhost/statistics/api_based/index/3 , so therefore I am using segments.
Now the problem is the query that is generated:
SELECT * FROM `shield_api` ORDER BY `id` ASC limit 20 offset 2
offset 2 - is the page number 2, and it should be 20, if I am correct.
I am displaying 20 results per page, so first page is correctly showing results from 1 - 20, but then page 2 will display results from 2 - 21, so you get my point, it`s not OK...
Here is my controller:
function index($offset = 0) {
// Enable SSL?
maintain_ssl ( $this->config->item ( "ssl_enabled" ) );
// Redirect unauthenticated users to signin page
if (! $this->authentication->is_signed_in ()) {
redirect ( 'account/sign_in/?continue=' . urlencode ( base_url () . 'statistics/api_based' ) );
}
if ($this->authentication->is_signed_in ()) {
$data ['account'] = $this->account_model->get_by_id ( $this->session->userdata ( 'account_id' ) );
}
$per_page = 20;
$qry = "SELECT * FROM `shield_api` ORDER BY `id` ASC";
//$offset = ($this->uri->segment ( 4 ) != '' ? $this->uri->segment ( 4 ) : 0);
$config ['total_rows'] = $this->db->query ( $qry )->num_rows ();
$config ['per_page'] = $per_page;
$config ['uri_segment'] = 4;
$config ['base_url'] = base_url () . '/statistics/api_based/index';
$config ['use_page_numbers'] = TRUE;
$config ['page_query_string'] = FALSE;
$config ['full_tag_open'] = '<ul class="pagination">';
$config ['full_tag_close'] = '</ul>';
$config ['prev_link'] = '«';
$config ['prev_tag_open'] = '<li>';
$config ['prev_tag_close'] = '</li>';
$config ['next_link'] = '»';
$config ['next_tag_open'] = '<li>';
$config ['next_tag_close'] = '</li>';
$config ['cur_tag_open'] = '<li class="active"><a href="#">';
$config ['cur_tag_close'] = '</a></li>';
$config ['num_tag_open'] = '<li>';
$config ['num_tag_close'] = '</li>';
$config ["num_links"] = round ( $config ["total_rows"] / $config ["per_page"] );
$this->pagination->initialize ( $config );
$data ['pagination_links'] = $this->pagination->create_links ();
//$data ['per_page'] = $this->uri->segment ( 4 );
$data ['offset'] = $offset;
$qry .= " limit {$per_page} offset {$offset} ";
if ($data ['pagination_links'] != '') {
$data ['pagermessage'] = 'Showing ' . ((($this->pagination->cur_page - 1) * $this->pagination->per_page) + 1) . ' to ' . ($this->pagination->cur_page * $this->pagination->per_page) . ' results, of ' . $this->pagination->total_rows;
}
$data ['result'] = $this->db->query ( $qry )->result_array ();
$this->load->view ( 'statistics/api_based', isset ( $data ) ? $data : NULL );
}
Can someone point me to my problem ? Any help is apreciated.
You may have a misunderstanding of how LIMIT works.
Limit with one value sets the maximum number to be returned.
LIMIT 10
Will retrieve at most 10 rows, starting at the beginning of the results matching your query.
Limit with two values sets the start position (offset in rows, not pages) and the maximum number of rows to be returned.
LIMIT 20, 10
Will retrieve at most 10 rows, starting at row 20.
So, you'll need to modify your logic a bit here:
$start = max(0, ( $offset -1 ) * $per_page);
$qry .= ' LIMIT {$start}, {$per_page}';

CI - Pass a variable from 1 controller to two models/views

Can anyone explain why I cannot get a variable called $siteID to pass to another function within the same controller?
In the third function called "get_orders_by_site" I have loaded a different model, which returns information about 'orders' raised at the currently viewed building/site/property.
The sites controller works perfectly, first function lists a table with all my properties, then when one is clicked - the second function gets the siteID of that selection, and returns with further 'detail/data' - sites controller function1/2 all relate to the same model, and return information from the SAME table.
I'm trying to implement a third function, which will do a similar task, but return with information/data from a different table (the site.siteID, is also a FK in the orders.siteID table i've created in phpmyadmin).
If I need to explain further please let me know - Many thanks!
amended code
Sites Controller
<?php
class Sites extends CI_Controller {
//Searches for a list of sites
public function search($sort_by = 'site_title', $sort_order = 'asc', $offset = 0)
{
$limit = 20;
$data['columns'] = array(
'site_title' => 'Site Name',
'site_uprn' => 'Unique Property Reference'
);
$this->load->model('site_model');
$results = $this->site_model->get_sites($limit, $offset, $sort_by, $sort_order);
$data['sites'] = $results['rows'];
$data['num_results'] = $results['num_rows'];
//pagination for list returned
$this->load->library('pagination');
$config = array ();
$config['base_url'] = site_url("Sites/search/$sort_by/$sort_order");
$config['total_rows'] = $data['num_results'];
$config['per_page'] = $limit;
$config['uri_segment'] = 5;
$this->pagination->initialize($config);
$data['pagination'] = $this->pagination->create_links();
$data['sort_by'] = $sort_by;
$data['sort_order'] = $sort_order;
$this->load->view('search', $data);
}
//Displays individual site details
//passes selected siteID to the model, and returns only database/info for that particular building/property
public function details($siteID){
$this->load->model('site_model');
$data['site']=$this->site_model->get_site($siteID);
$this->load->view('site', $data);
$this->load->view('orders', $data);
}
// this second function should do a similar method as above, however I've loaded a different model, as i'm getting information from a different database table - but I still want the data returned to be limited by the building/site ID which the user selects.
public function orders_by_site($siteID, $sort_by = 'orderID', $sort_order = 'asc', $offset = 0)
{
$this->load->model('site_model');
$this->load->model('order_model');
$limit = 20;
$data['columns'] = array(
'orderID' => 'Order No.',
'initiated_date' => 'Initiated Date',
'target_date' => 'Target Date',
'status' => 'Status',
'priority' => 'Priority',
'trade_type' => 'Trade Type'
);
$results = $this->site_model->get_site($siteID);
$results = $this->order_model->get_orders($siteID, $limit, $offset, $sort_by, $sort_order);
$data['orders'] = $results['rows'];
$data['num_results'] = $results['num_rows'];
//pagination for orders table
$this->load->library('pagination');
$config = array ();
$config['base_url'] = site_url("Orders/orders_by_site/$sort_by/$sort_order");
$config['total_rows'] = $data['num_results'];
$config['per_page'] = $limit;
$config['uri_segment'] = 6;
$this->pagination->initialize($config);
$data['pagination'] = $this->pagination->create_links();
$data['sort_by'] = $sort_by;
$data['sort_order'] = $sort_order;
$this->load->view('orders', $data);
}
}
End Sites Controller
Site Model
//Get all site data
function get_sites($limit, $offset, $sort_by, $sort_order){
$sort_order = ($sort_order == 'desc') ? 'desc' : 'asc';
$sort_columns = array('site_title', 'site_uprn');
$sort_by = (in_array($sort_by, $sort_columns)) ? $sort_by : 'site_title';
$q = $this->db->select('siteID, site_title, site_uprn')
->from('sites')
->limit($limit, $offset)
->order_by($sort_by, $sort_order);
$ret['rows'] = $q->get()->result();
//count query for sites
$q = $this->db->select('COUNT(*) as count', FALSE)
->from('sites');
$tmp = $q->get()->result();
$ret['num_rows'] = $tmp[0]->count;
return $ret;
}
//Get individual site data
function get_site($siteID){
$this->db->select()->from('sites')->where(array('siteID' => $siteID));
$query = $this->db->get();
return $query->first_row('array');
}
}
Orders Model
//order table
function get_orders($siteID, $orderID, $limit, $offset, $sort_by, $sort_order){
$sort_order = ($sort_order == 'desc') ? 'desc' : 'asc';
$sort_columns = array('orderID', 'initiated_date', 'target_date','completion_date','status','priority','total_amount','job_description','requestor_name','requestor_telno','trade_type');
$sort_by = (in_array($sort_by, $sort_columns)) ? $sort_by : 'orderID';
$q = $this->db->select()->from('orders')->where(array('siteID' => $siteID))->limit($limit, $offset)->order_by($sort_by, $sort_order);
$ret['rows'] = $q->get()->result();
}
//order details
function get_order($orderID){
$this->db->select()->from('orders')->where(array('orderID' => $orderID));
$query = $this->db->get();
return $query->first_row('array');
}
}
Site View - only showing the extract where I'm trying to embed the orders view
<h5>Order Details</h5>
<?php include('orders.php')?>
</div>
</div>
</div>
Orders View
<div id="site_filter">
<div class="result_counter">
<h5>Found <?php echo $num_results; ?> Orders</h5>
</div>
<div class="pagination">
<?php if(strlen($pagination)): ?>
Page: <?php echo $pagination; ?>
<?php endif; ?>
</div>
</div>
<div class="clear_float"></div>
<table class="table">
<thead>
<?php foreach($columns as $column_name => $column_display): ?>
<th <?php if ($sort_by == $column_name) echo "class=\"sort_$sort_order\"" ?>>
<?php echo anchor("Sites/orders_by_site/$column_name/" .
(($sort_order == 'asc' && $sort_by == $column_name) ? 'desc' : 'asc') ,
$column_display); ?>
</th>
<?php endforeach; ?>
</thead>
<tbody>
<?php foreach($orders as $order): ?>
<tr>
<td><?php echo $order->orderID; ?></td>
<td><?php echo $order->initiated_date; ?></td>
<td><?php echo $order->target_date; ?></td>
<td><?php echo $order->status; ?></td>
<td><?php echo $order->priority; ?></td>
<td><?php echo $order->trade_type; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
OK, a couple of things here - and I'm gonna need some help
In the orders_by_site method, you're overwriting the results variable:
$results = $this->site_model->get_site($siteID);
$results = $this->order_model->get_orders($siteID, $limit, $offset, $sort_by, $sort_order);
Also, the way you're loading views is incorrect. You're loading your views like this:
$this->load->view('site', $data);
$this->load->view('orders', $data);
And what you need to be doing is this:
// you need to get the "contents" of the `orders` view in a variable
// and pass that to the `site` view
$data['orders'] = $this->load->view('orders', $data, TRUE);
$this->load->view('site', $data);
And change your site view to this:
<h5>Order Details</h5>
<?php echo $orders; ?>
</div>
</div>
</div>
I'm sure there's more going on than that, but that's all I can gather from what I've seen in your question and comments.
#swatkins -
Thank you very much for your help, you highlighted some issues I had overlooked - I've gone about this in a different fashion now.
Originally I was trying to use Active Record (i believe) to select data from one table, based on the selection of a record from a different table - and then pass this selection to another model and use it in a get_where statement.
I've managed to get this to work using the $this->uri->segment() method in my controller, and then passing this to the corresponding model.
Now I'm able to utilise the user selection of a 'building/propery' name, with it's address etc - and then I have a second model, which retrieves the 'orders/jobs' that have been raised at that building.

Categories