So, I am inheriting an intranet site that was built on datatables. Basically it is made in Wordpress and then displays custom fields that the users have filled out in the datatable. But it was querying 40 columns per post over 2k entries so is now grinding to a standstill when the users try and view the data in the table.
I'm attempting to utilize the server-side aspect of Datatables for this, but running into a little trouble because of how the sql data is formatted.
Can anyone offer any assistance in how to set up the server_processing.php file (i'm using this one: http://datatables.net/development/server-side/php_mysql) to:
Display rows based on the wp_posts.ID index
Display columns in this row based on a different table (wp_postmeta) wherein each column value is indexed separately in the wp_postmeta table by the ID found in 1
Link entire row to a url found in the wp_posts table
If anyone has any ideas I would really appreciate it...
Definitely a plugin for WordPress is most efficient. AJAX can conflict/cause problems with the JQuery that is core to using WordPress edit functions etc. I'd go to WordPress.org and look under plugins for help there. There are also paid/premium intranet plugins such as Simple Intranet. :)
Chris
Probably you might be interested in this plugin - looks like it does exactly what you're looking for: http://wpdatatables.com
An example with server-side processing: http://wpdatatables.com/display-mysql-table-in-wordpress/
You will definitely need a Wordpress table plugin that wraps DataTables functionality. You may consider Tabulizer for Wordpress (http://www.tabulizer.com) that supports server side processing and loads table data via Ajax calls only when needed. There is also a data source cache for improved performance.
Ajax dataTable or server-side processing dataTable in WordPress or Core PHP.
HTML Code
<table id="student_table" width="100%">
<thead>
<tr>
<th>Roll No.</th>
<th>Full Name</th>
<th>Phone</th>
<th>Action</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>Roll No.</th>
<th>Full Name</th>
<th>Phone</th>
<th>Action</th>
</tr>
</tfoot>
</table>
jQuery code
jQuery('#student_table').DataTable({
"bProcessing": true,
"serverSide": true,
"ajax":{
"url": FrontendConfig.ajaxurl+'?action=getStudentsFromExamIdAjax&exam_nounce=exam_nounce_data&exam_id=1',
type: "post",
}
});
WordPress or PHP code
add_action('wp_ajax_getStudentsFromExamIdAjax', 'getStudentsFromExamIdAjax' );
add_action('wp_ajax_nopriv_getStudentsFromExamIdAjax', 'getStudentsFromExamIdAjax' );
function getStudentsFromExamIdAjax(){
if(empty($_GET['action']) || empty($_GET['exam_id'])){
wp_send_json_error( new \WP_Error( 'Bad Request' ) );
}
if(isset($_GET['exam_id']) && $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( $_GET['exam_nounce'], 'exam_nounce_data' )):
$exam_id = (isset($_GET['exam_id'])) ? absint($_GET['exam_id']) : '';
/*# You can create a function to get the data here */
$students = getStudentsFromExamId($exam_id);
$tdata = [];
foreach ($students as $key => $value):
$tdata[$key][] = $value->roll_no;
$tdata[$key][] = $value->name;
$tdata[$key][] = $value->phone;
$tdata[$key][] = 'action here';
endforeach;
$total_records = count($tdata);
$json_data = array(
/* $_REQUEST['draw'] comes from the datatable, you can print to ensure that */
"draw" => intval( $_REQUEST['draw'] ),
"recordsTotal" => intval( $total_records ),
"recordsFiltered" => intval( $total_records ),
"data" => $tdata
);
echo json_encode($json_data);
endif;
wp_die();
}
Custom.js
jQuery( document ).ready(function() {
alert('test');
jQuery('#employee_grid').DataTable({
"bProcessing": true,
"serverSide": true,
"ajax":{
url :ajax_url_object.ajax_url+'?action=getStudentsFromExamIdAjax&exam_nounce=exam_nounce_data&exam_id=1', // json datasource
type: "POST", // type of method ,GET/POST/DELETE
error: function(){
jQuery("#employee_grid_processing").css("display","none");
}
}
}); });
WordPress or PHP code
function theme_styles1()
{
$template_dir_uri =content_url();
$pluginurl =plugins_url();
//====================js
wp_enqueue_style( 'vpn-custom-style1','https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.css', true );
wp_enqueue_style( 'vpn-custom-boostrap1','https://cdn.datatables.net/1.11.3/css/dataTables.bootstrap4.min.css', true );
wp_enqueue_script( 'vpn-script11','https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js', true );
wp_enqueue_script( 'vpn-script21','https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap4.min.js', true );
wp_enqueue_script( 'vpn_script31', plugin_dir_url( __FILE__ ).'js/custom.js', true );
//============ ajax url
$urls= array('ajax_url'=> admin_url( 'admin-ajax.php' ),
'site_url' => site_url()
);
wp_localize_script( 'vpn_script31', 'ajax_url_object',$urls);
}
add_action( 'wp_enqueue_scripts', 'theme_styles1',99 );
div class="wrap">
<h1>Service List</h1>
<div class="container">
<table id="employee_grid" class="display" width="100%" cellspacing="0">
<thead>
<tr>
<th>Empid</th>
<th>Name</th>
<th>Salary</th>
<th>Age</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Empid</th>
<th>Name</th>
<th>Salary</th>
<th>Age</th>
</tr>
</tfoot>
</table>
</div>
add_action('wp_ajax_getStudentsFromExamIdAjax', 'getStudentsFromExamIdAjax' );
add_action('wp_ajax_nopriv_getStudentsFromExamIdAjax', 'getStudentsFromExamIdAjax' );
function getStudentsFromExamIdAjax(){
global $wp;
$servername = "localhost:3308";
$username = "root";
$password = "";
$dbname = "demo";
$conn = mysqli_connect($servername, $username, $password, $dbname) or die("Connection failed: " . mysqli_connect_error());
$params = $columns = $totalRecords = $data = array();
$params = $_REQUEST;
// echo "<pre>";
// print_r($params);
// die;
//define index of column
$columns = array(
0 =>'id',
1 =>'employee_name',
2 => 'employee_salary',
3 => 'employee_age'
);
$where = $sqlTot = $sqlRec = "";
// check search value exist
if( !empty($params['search']['value']) ) {
$where .=" WHERE ";
$where .=" ( employee_name LIKE '".$params['search']['value']."%' ";
$where .=" OR employee_salary LIKE '".$params['search']['value']."%' ";
$where .=" OR employee_age LIKE '".$params['search']['value']."%' )";
}
// getting total number records without any search
$sql = "SELECT * FROM `employee` ";
$sqlTot .= $sql;
$sqlRec .= $sql;
//concatenate search sql if value exist
if(isset($where) && $where != '') {
$sqlTot .= $where;
$sqlRec .= $where;
}
$sqlRec .= " ORDER BY ". $columns[$params['order'][0]['column']]." ".$params['order'][0]['dir']." LIMIT ".$params['start']." ,".$params['length']." ";
$queryTot = mysqli_query($conn, $sqlTot) or die("database error:". mysqli_error($conn));
$totalRecords = mysqli_num_rows($queryTot);
$queryRecords = mysqli_query($conn, $sqlRec) or die("error to fetch employees data");
//iterate on results row and create new index array of data
while( $row = mysqli_fetch_row($queryRecords) ) {
$data[] = $row;
}
// echo "<pre>";
// print_r($data);
// die;
$json_data = array(
"draw" => intval( $params['draw'] ),
"recordsTotal" => intval( $totalRecords ),
"recordsFiltered" => intval($totalRecords),
"data" => $data // total data array
);
echo json_encode($json_data); // send data as json format
}
Related
Originally the html that is output is:
<tr class="order_item">
<td class="td">
Product Name
<ul class="wc-item-meta">
<li>
<strong class="wc-item-meta-label">Label:</strong>
<p>Value</p>
</li>
<!-- several more li-->
</ul>
</td>
<td class="td">1 </td>
<td class="td">
<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">CHF</span>49.00</span>
</td>
</tr>
I want to get rid of this unwieldy and mobile-unfriendly ul, li and p structure and simplify the html like so:
<tr class="order_item">
<th>Model</th>
<td>Model Name</td>
</tr>
<tr>
<th>Label</th>
<td>Value</td>
</tr>
etc.
My first move is to get rid of the thead-tag in email-order-details.php. I prefer labels like the th-tag.
I also need to change the html structure in email-order-items.php
But then i get stuck with the function wc_display_item_meta. I don't know how to make this function display the desired html markup displayed above.
function wc_display_item_meta( $item, $args = array() ) {
$strings = array();
$html = '';
$args = wp_parse_args(
$args,
array(
'before' => '<tr class="order_item">',
'after' => '</tr>',
'separator' => '</tr><tr>',
'value_before' => '<td class="wc-item-meta-value">',
'value_after' => '</td>',
'echo' => true,
'autop' => false,
'my_label_before' => '<th class="wc-item-meta-label">',
'my_label_after' => ':</th> ',
)
);
foreach ( $item->get_formatted_meta_data() as $meta_id => $meta ) {
$value = $args['autop'] ? wp_kses_post( $meta->display_value ) : wp_kses_post( make_clickable( trim( $meta->display_value ) ) );
$strings[] = $args['my_label_before'] . wp_kses_post( $meta->display_key ) . $args['my_label_after'] . $args['value_before'] . $value . $args['value_before'];
}
if ( $strings ) {
$html = $args['before'] . implode( $args['separator'], $strings ) . $args['after'];
}
$html = apply_filters( 'woocommerce_display_item_meta', $html, $item, $args );
if ( $args['echo'] ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html;
} else {
return $html;
}
}
How do i change the foreach loop to get my desired html output display below?
<tr>
<th></th>
<td></td>
</tr>
The function wc_display_item_meta mentioned above works. I wasn’t aware, that i was on the right track.
But it is better to name it differently, and then call it in «email-order-items.php». This way, other uses of wc_display_item_meta (for instance in «order/order-details-item.php» won’t be affected be the markup, that is exclusively for e-mails).
Therefore in functions.php i have my special e-mail html markup function:
function my_display_item_meta( $item, $args = array() ) {
etc.
}
and in «email-order-items.php» i call my function instead of wc_display_item meta:
my_display_email_item_meta($item);
Note: You may look closely at the mark up of your e-mail templates in order to achieve a result that suits your requirements. And since the html changes, the email styles need to be updated.
In my case the e-mail messages generated by woocommerce look better on mobile phones and it is much easier to render the message according to the screen, that is responsive.
To sum up, i shouldn’t have asked in the first place. This is only wasting resources. However, i won’t delete the post. But if admin decides otherwise, i wouldn’t mind. Ciao
My intention is to hide orders that contains a specific product id (5).
This in the WooCommmerce "My account" orders table
What I have done are the steps as described in the /myaccount/orders.php
template file. Namely copied the file to my theme folder.
(mytheme/woocommerce/myaccount/orders.php.)
There I replaced
<tbody>
<?php
foreach ( $customer_orders->orders as $customer_order ) {
$order = wc_get_order( $customer_order ); // phpcs:ignore
WordPress.WP.GlobalVariablesOverride.Prohibited
$item_count = $order->get_item_count() -
$order->get_item_count_refunded();
?>
<tr class="woocommerce-orders-table__row
woocommerce-orders-table__row--status-<?php echo esc_attr(
$order->get_status() ); ?> order">
...
</tr>
<?php
}
?>
</tbody>
With
<tbody>
<?php
foreach ( $customer_orders->orders as $customer_order ) {
$order = wc_get_order( $customer_order ); // phpcs:ignore
WordPress.WP.GlobalVariablesOverride.Prohibited
$item_count = $order->get_item_count() -
$order->get_item_count_refunded();
foreach( $order->get_items() as $item ) {
$product_id = $item->get_product_id();
if ( $product_id != 5 ) {
?>
<tr class="woocommerce-orders-table__row
woocommerce-orders-table__row--status-<?php echo esc_attr(
$order->get_status() ); ?> order">
...
</tr>
<?php
}
}
}
?>
</tbody>
Although this has no error messages, it does not produce the desired result.
Can someone give some advice? oh, and if there is a solution via hooks instead of
overwriting the template file I'd really appreciate it.
To answer your question via hooks, you can use the woocommerce_my_account_my_orders_query filter hook.
The code below is copied from /includes/wc-template-functions.php line 3159 - 3185 #version 2.5.0
/**
* My Account > Orders template.
*
* #param int $current_page Current page number.
*/
function woocommerce_account_orders( $current_page ) {
$current_page = empty( $current_page ) ? 1 : absint( $current_page );
$customer_orders = wc_get_orders(
apply_filters(
'woocommerce_my_account_my_orders_query',
array(
'customer' => get_current_user_id(),
'page' => $current_page,
'paginate' => true,
)
)
);
wc_get_template(
'myaccount/orders.php',
array(
'current_page' => absint( $current_page ),
'customer_orders' => $customer_orders,
'has_orders' => 0 < $customer_orders->total,
)
);
}
As you can see, via the hook, you can modify the arguments used by the wc_get_orders function. Which in turn is used by the wc_get_template function. The function that calls the /myaccount/orders.php template file and pass the arguments.
wc_get_orders allows us to exclude orderIDs. But no productIDs. That's why we're going to use a workaround so we can still pass the necessary orderIDs to exclude as argument.
The function to get all orders IDs for a given product ID, processed in my answer is based on Woocommerce: Get all orders for a product answer code.
So to answer your question, you get:
function get_orders_ids_by_product_id( $product_id ) {
global $wpdb;
$statuses = array_keys( wc_get_order_statuses() );
$statuses = implode( "','", $statuses );
$results = $wpdb->get_col("
SELECT order_items.order_id
FROM {$wpdb->prefix}woocommerce_order_items as order_items
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
LEFT JOIN {$wpdb->posts} AS posts ON order_items.order_id = posts.ID
WHERE posts.post_type = 'shop_order'
AND posts.post_status IN ('$statuses')
AND order_items.order_item_type = 'line_item'
AND order_item_meta.meta_key = '_product_id'
AND order_item_meta.meta_value = '$product_id'
");
return $results;
}
function filter_woocommerce_my_account_my_orders_query( $args ) {
// Get all orders IDs for a given product ID
$orders_ids = get_orders_ids_by_product_id( 5 );
// Existing args
$customer = $args['customer'];
$current_page = $args['page'];
$paginate = $args['paginate'];
// Get orders that aren't the current order.
$args = array(
'customer' => $customer,
'page' => $current_page,
'paginate' => $paginate,
'exclude' => $orders_ids,
);
return $args;
}
add_filter( 'woocommerce_my_account_my_orders_query', 'filter_woocommerce_my_account_my_orders_query', 10, 1 );
If you look at WC_Order->get_items this retuns WC_Order_Item not always WC_Order_Item_Product (which has get_product_id). So I would try something like this:
foreach ($customer_orders as $customer_order) {
$order = wc_get_order($customer_order); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$item_count = $order->get_item_count();
// Always show orders by default
$show_order = true;
// Check if order has specific product ids
foreach ($order->get_items() as $item) {
if (is_a($item, 'WC_Order_Item_Product')) {
$product_id = $item->get_product_id();
$product_ids = array(5); // Enter product ids here
if (in_array($product_id, $product_ids)) {
// Hide order
$show_order = false;
}
}
}
if ($show_order) {
// <tr>
// ...
// </tr>
}
}
I have a page with the following code in it, this code is duplicated from the dashboard. This means this particular widget now shows in two places. With help I managed to increase the number of days this report shows to 60 however I only want the dashboard version to show 60 days and I would like this version to show 365 days.
<?php
/*
* The template for displaying vendor pending shipping table dashboard widget
* Override this template by copying it to yourtheme/dc-product-vendor/vendor-dashboard/dashboard-widgets/wcmp_vendor_product_sales_report.php
*
* #author WC Marketplace
* #package WCMp/Templates
* #version 3.0.0
*/
if (!defined('ABSPATH')) {
// Exit if accessed directly
exit;
}
global $WCMp;
$product_sales_report_table_headers = apply_filters('wcmp_datatable_widget_product_sales_report_table_headers', array(
'product' => array('label' => __( 'Product', 'dc-woocommerce-multi-vendor' )),
// 'revenue' => array('label' => __( 'Revenue', 'dc-woocommerce-multi-vendor' )),
'unique_purchase'=> array('label' => __( 'Unique Purchases', 'dc-woocommerce-multi-vendor' )),
), get_current_user_id());
?>
<p>This page shows all your designs that have sold over the past year.</p>
<table id="widget_product_sales_report" class="table table-striped product_sold_last_week table-bordered wcmp-widget-dt" width="100%">
<thead>
<tr>
<?php
if($product_sales_report_table_headers) :
foreach ($product_sales_report_table_headers as $key => $header) { ?>
<th class="<?php if(isset($header['class'])) echo $header['class']; ?>"><?php if(isset($header['label'])) echo $header['label']; ?></th>
<?php }
endif;
?>
<!--th><?php _e('Product', 'dc-woocommerce-multi-vendor'); ?></th>
<th><?php _e('Revenue', 'dc-woocommerce-multi-vendor'); ?></th>
<th><?php _e('Unique Purchases', 'dc-woocommerce-multi-vendor'); ?></th-->
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
jQuery(document).ready(function($) {
var product_sales_report_wgt;
var columns = [];
<?php if($product_sales_report_table_headers) {
foreach ($product_sales_report_table_headers as $key => $header) { ?>
obj = {};
obj['data'] = '<?php echo esc_js($key); ?>';
obj['className'] = '<?php if(isset($header['class'])) echo esc_js($header['class']); ?>';
columns.push(obj);
<?php }
} ?>
product_sales_report_wgt = $('#widget_product_sales_report').DataTable({
ordering : true,
paging: true,
info: true,
searching : true,
processing: false,
serverSide: true,
responsive: true,
language: {
"emptyTable": "<?php echo trim(__('Not enough data.','dc-woocommerce-multi-vendor')); ?>",
"zeroRecords": "<?php echo trim(__('Not enough data.','dc-woocommerce-multi-vendor')); ?>",
},
ajax:{
url : '<?php echo add_query_arg( 'action', 'wcmp_widget_vendor_product_sales_report', $WCMp->ajax_url() ); ?>',
type: "post",
error: function(xhr, status, error) {
$("#widget_product_sales_report tbody").append('<tr class="odd"><td valign="top" colspan="<?php if(is_array($product_sales_report_table_headers)) count($product_sales_report_table_headers); ?>" class="dataTables_empty" style="text-align:center;">'+error+' - <?php _e('Reload', 'dc-woocommerce-multi-vendor'); ?></td></tr>');
$("#widget_product_sales_report").css("display","none");
}
},
columns: columns
});
new $.fn.dataTable.FixedHeader( product_sales_report_wgt );
});
</script>
Using this code
// To change the default value of `$days_range` from 7 days to 60 days
function lh_wcmp_vendor_custom_sales_report( $days_range ) {
$days_range = 60; // you can adjust days here as you needed
return $days_range;
}
add_filter( 'wcmp_widget_vendor_product_sales_report_days_range', 'lh_wcmp_vendor_custom_sales_report', 10 );
I was able to change the default 7 days to 60. But because I am using the same widget code both the dashboard and the report show the same amount of days.
So, to summerise - what I would like is to show 60 days on the dashboard (using the code above) and then show 365 days on the report page.
Can anyone help?
You need to update the code in \basel-child\dc-product-vendor\vendor-dashboard\vendor-orders.php and \basel-child\functions.php files.
We are going to add a new query string while making the AJAX request to wcmp_widget_vendor_product_sales_report function from vendor-orders endpoint. It will allow us to detect whether the request is coming from Dashboard or Vendor Orders endpoint.
Following code will be added in the vendor-orders.php file (see line 67-79):
<?php
/* Add 'lh-endpoint' => 'lh-vendor-orders' to the AJAX URL, so we can use it later
* to change the '$days_range' value conditionally. It will change the ajax url from
* /wp-admin/admin-ajax.php?action=wcmp_widget_vendor_product_sales_report to
* /wp-admin/admin-ajax.php?action=wcmp_widget_vendor_product_sales_report&lh-endpoint=lh-vendor-orders
*/
$args = array(
'action' => 'wcmp_widget_vendor_product_sales_report',
'lh-endpoint' => 'lh-vendor-orders',
);
?>
url : '<?php echo add_query_arg( $args, $WCMp->ajax_url() ); ?>',
Then we will modify our old lh_wcmp_vendor_custom_sales_report() function in functions.php file as:
// get the value of 'lh-endpoint' from URL
$lh_orders_endpoint = isset( $_GET['lh-endpoint'] ) && !empty( $_GET['lh-endpoint'] ) ? $_GET['lh-endpoint'] : '';
// check if 'lh-endpoint' value is 'lh-vendor-orders' or not
if ( 'lh-vendor-orders' !== $lh_orders_endpoint ) {
$days_range = 60; // if 'lh-endpoint' is not 'lh-vendor-orders', then show last 2 months orders
} else {
$days_range = 365; // if 'lh-endpoint' is 'lh-vendor-orders', then show orders from 1 year
}
Here is the complete code for both files:
Remove the existing code from vendor-orders.php file and put this code:
<?php
/*
* The template for displaying vendor orders
* Override this template by copying it to yourtheme/dc-product-vendor/vendor-dashboard/vendor-orders.php
*
* #author WC Marketplace
* #package WCMp/Templates
* #version 3.0.0
*/
if (!defined('ABSPATH')) {
// Exit if accessed directly
exit;
}
global $WCMp;
$product_sales_report_table_headers = apply_filters('wcmp_datatable_widget_product_sales_report_table_headers', array(
'product' => array('label' => __( 'Product', 'dc-woocommerce-multi-vendor' )),
// 'revenue' => array('label' => __( 'Revenue', 'dc-woocommerce-multi-vendor' )),
'unique_purchase'=> array('label' => __( 'Unique Purchases', 'dc-woocommerce-multi-vendor' )),
), get_current_user_id());
?>
<p>This page shows all your designs that have sold over the past year.</p>
<table id="widget_product_sales_report" class="table table-striped product_sold_last_week table-bordered wcmp-widget-dt" width="100%">
<thead>
<tr>
<?php
if($product_sales_report_table_headers) :
foreach ($product_sales_report_table_headers as $key => $header) { ?>
<th class="<?php if(isset($header['class'])) echo $header['class']; ?>"><?php if(isset($header['label'])) echo $header['label']; ?></th>
<?php }
endif;
?>
<!--th><?php _e('Product', 'dc-woocommerce-multi-vendor'); ?></th>
<th><?php _e('Revenue', 'dc-woocommerce-multi-vendor'); ?></th>
<th><?php _e('Unique Purchases', 'dc-woocommerce-multi-vendor'); ?></th-->
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
jQuery(document).ready(function($) {
var product_sales_report_wgt;
var columns = [];
<?php if($product_sales_report_table_headers) {
foreach ($product_sales_report_table_headers as $key => $header) { ?>
obj = {};
obj['data'] = '<?php echo esc_js($key); ?>';
obj['className'] = '<?php if(isset($header['class'])) echo esc_js($header['class']); ?>';
columns.push(obj);
<?php }
} ?>
product_sales_report_wgt = $('#widget_product_sales_report').DataTable({
ordering : true,
paging: true,
info: true,
searching : true,
processing: true,
serverSide: true,
responsive: true,
language: {
"emptyTable": "<?php echo trim(__('Not enough data.','dc-woocommerce-multi-vendor')); ?>",
"zeroRecords": "<?php echo trim(__('Not enough data.','dc-woocommerce-multi-vendor')); ?>",
},
ajax:{
<?php
/* Add 'lh-endpoint' => 'lh-vendor-orders' to the AJAX URL, so we can use it later
* to change the '$days_range' value conditionally. It will change the ajax url from
* /wp-admin/admin-ajax.php?action=wcmp_widget_vendor_product_sales_report to
* /wp-admin/admin-ajax.php?action=wcmp_widget_vendor_product_sales_report&lh-endpoint=vendor-orders
*/
$args = array(
'action' => 'wcmp_widget_vendor_product_sales_report',
'lh-endpoint' => 'lh-vendor-orders',
);
?>
//url : '<?php //echo add_query_arg( 'action', 'wcmp_widget_vendor_product_sales_report', $WCMp->ajax_url() ); ?>',
url : '<?php echo add_query_arg( $args, $WCMp->ajax_url() ); ?>',
type: "post",
error: function(xhr, status, error) {
$("#widget_product_sales_report tbody").append('<tr class="odd"><td valign="top" colspan="<?php if(is_array($product_sales_report_table_headers)) count($product_sales_report_table_headers); ?>" class="dataTables_empty" style="text-align:center;">'+error+' - <?php _e('Reload', 'dc-woocommerce-multi-vendor'); ?></td></tr>');
$("#widget_product_sales_report").css("display","none");
}
},
columns: columns
});
new $.fn.dataTable.FixedHeader( product_sales_report_wgt );
});
</script>
In your functions.php file, replace the old lh_wcmp_vendor_custom_sales_report() function with the new one:
// Conditionally change the default value of `$days_range` from 7 days to 60 or 365 days
function lh_wcmp_vendor_custom_sales_report( $days_range ) {
// get the value of 'lh-endpoint' from URL
$lh_orders_endpoint = isset( $_GET['lh-endpoint'] ) && !empty( $_GET['lh-endpoint'] ) ? $_GET['lh-endpoint'] : '';
// check if 'lh-endpoint' value is 'lh-vendor-orders' or not
if ( 'lh-vendor-orders' !== $lh_orders_endpoint ) {
$days_range = 60; // if 'lh-endpoint' is not 'lh-vendor-orders', then show last 2 months orders
} else {
$days_range = 365; // if 'lh-endpoint' is 'lh-vendor-orders', then show orders from 1 year
}
return $days_range;
}
add_filter( 'wcmp_widget_vendor_product_sales_report_days_range', 'lh_wcmp_vendor_custom_sales_report' );
See the screenshots of locally hacked version of WC Marketplace plugin:
wcmp_widget_vendor_product_sales_report call from Vendor Dashboard
wcmp_widget_vendor_product_sales_report call from Vendor Orders Tab
Tested and working on:
WordPress 5.0.3
Twentyninteen 1.2
WooCommerce 3.5.4
WC Marketplace 3.3.1
Localhost (XAMPP for Windows 5.6.15)
I am developing a simple WordPress plugin where admin can create user roles using an admin form. I want to display an admin_notice if a role is already existing.
Problem I am having is even the role exists, the message is not appearing. If I understand properly, this is because the form is actually being redirected to admin-post.php. If so, how can I show the admin_notice after submission?
In short I have this:
One function in my plugin to render the form.
One function to save. Before saving, WP checks whether role exists. If yes, I want to display an admin_notice and "return". Otherwise, role gets saved.
The code snippet to check and show admin_notice:
<?php
function save_user_role()
{
$role_name = $_POST['role_name'];
$role_slug = sanitize_title_with_dashes($role_name);
$role = get_role($role_slug);
if (!empty($role))
{
$notice = 'Role exists';
do_action('admin_notices', $this->display_notice($notice));
// return; // If I use the return the correct message coming up in a blank page.
}
else
{
// Save the role
}
}
Below is the full code that I have written
function save_user_role() {
// Role already exists?
$role_name = $_POST[ 'coolmedia_role_name' ];
$role_slug = sanitize_title_with_dashes( $role_name );
$role = get_role( $role_slug );
if( ! empty( $role ) ) {
// Error. Role already exists
$notice = 'Speficed role ' . $role_name . ' already exists';
do_action( 'admin_notices', $this->display_notice( $notice ) );
} else {
// Safe to create the new role
$role_caps = array(
'read' => true,
);
...
add_role( $role_slug, esc_html( $role_name ), $role_caps );
// Redirect back to form page
wp_redirect( admin_url( 'admin.php?page=user-roles' ) );
}
}
Display notice function:
function add_user_role_markup() { ?>
<div class="wrap">
<h1>Add Role</h1>
<form method="post" action="<?php esc_html(admin_url('admin-post.php')); ?>">
<table class="form-table">
<tr class="first">
<th>Role name</th>
<td><input required type="text" id="role_name" name="role_name" /></td>
</tr>
<tr>
<th>
<?php
('coolmedia-role-save', 'coolmedia-role-nonce'); ?>
<input type="hidden" name="action" value="new_user_role">
</th>
<td><?php
('Save Role'); ?></td>
</tr>
</table>
</form>
</div>
<?php
}
It depends on when save_user_role is being called and what else happens after that function. It's possible that the admin_notices hook is firing, but that (as you suggest) there's a redirect happening which would cause it not to fire after the redirect because (rightfully) save_user_role isn't being called again, or that save_user_role is firing after the admin_notices hook is called (in which case you're too late).
One solution might be to temporarily store an option in the database with your notice, and then check for it and display it as needed if it exists. This way, WordPress can do its redirect and then check for and display your notice when it returns.
Something like this. (Note: my add_action assumes this is happening in a class which I assume is how you've structured it based on your use of $this->display_notice())
function save_user_role(){
...
if( ! empty( $role ) ) {
// Error. Role already exists
$notice = 'Specified role ' . $role_name . ' already exists';
update_option( 'coolmedia_role_exists_message', $notice, 'no' );
}
...
}
add_action( 'admin_notices', array( $this, 'maybe_display_notice' ) );
function maybe_display_notice(){
$notice = get_option( 'coolmedia_role_exists_message', false );
if( $notice ){
delete_option( 'coolmedia_role_exists_message' );
$this->display_notice( $notice );
}
}
function display_notice( $notice ){
...
}
I am using Wordpress 3.5, I have a custom post (sp_product) with a metabox and some input field. One of those input (sp_title).
I want to Search by the custom post title name by typing in my input (sp_title) field and when i press add button (that also in my custom meta box), It will find that post by that Title name and bring some post meta data into this Meta box and show into other field.
Here in this picture (Example)
Search
Click Button
Get some value by AJAX from a custom post.
Please give me a example code (just simple)
I will search a simple custom post Title,
Click a button
Get the Title of that post (that i search or match) with any other post meta value, By AJAX (jQuery-AJAX).
Please Help me.
I was able to find the lead because one of my plugins uses something similar to Re-attach images.
So, the relevant Javascript function is findPosts.open('action','find_posts').
It doesn't seem well documented, and I could only found two articles about it:
Find Posts Dialog Box
Using Built-in Post Finder in Plugins
Tried to implement both code samples, the modal window opens but dumps a -1 error. And that's because the Ajax call is not passing the check_ajax_referer in the function wp_ajax_find_posts.
So, the following works and it's based on the second article. But it has a security breach that has to be tackled, which is wp_nonce_field --> check_ajax_referer. It is indicated in the code comments.
To open the Post Selector, double click the text field.
The jQuery Select needs to be worked out.
Plugin file
add_action( 'load-post.php', 'enqueue_scripts_so_14416409' );
add_action( 'add_meta_boxes', 'add_custom_box_so_14416409' );
add_action( 'wp_ajax_find_posts', 'replace_default_ajax_so_14416409', 1 );
/* Scripts */
function enqueue_scripts_so_14416409() {
# Enqueue scripts
wp_enqueue_script( 'open-posts-scripts', plugins_url('open-posts.js', __FILE__), array('media', 'wp-ajax-response'), '0.1', true );
# Add the finder dialog box
add_action( 'admin_footer', 'find_posts_div', 99 );
}
/* Meta box create */
function add_custom_box_so_14416409()
{
add_meta_box(
'sectionid_so_14416409',
__( 'Select a Post' ),
'inner_custom_box_so_14416409',
'post'
);
}
/* Meta box content */
function inner_custom_box_so_14416409( $post )
{
?>
<form id="emc2pdc_form" method="post" action="">
<?php wp_nonce_field( 'find-posts', '_ajax_nonce', false); ?>
<input type="text" name="kc-find-post" id="kc-find-post" class="kc-find-post">
</form>
<?php
}
/* Ajax replacement - Verbatim copy from wp_ajax_find_posts() */
function replace_default_ajax_so_14416409()
{
global $wpdb;
// SECURITY BREACH
// check_ajax_referer( '_ajax_nonce' );
$post_types = get_post_types( array( 'public' => true ), 'objects' );
unset( $post_types['attachment'] );
$s = stripslashes( $_POST['ps'] );
$searchand = $search = '';
$args = array(
'post_type' => array_keys( $post_types ),
'post_status' => 'any',
'posts_per_page' => 50,
);
if ( '' !== $s )
$args['s'] = $s;
$posts = get_posts( $args );
if ( ! $posts )
wp_die( __('No items found.') );
$html = '<table class="widefat" cellspacing="0"><thead><tr><th class="found-radio"><br /></th><th>'.__('Title').'</th><th class="no-break">'.__('Type').'</th><th class="no-break">'.__('Date').'</th><th class="no-break">'.__('Status').'</th></tr></thead><tbody>';
foreach ( $posts as $post ) {
$title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
switch ( $post->post_status ) {
case 'publish' :
case 'private' :
$stat = __('Published');
break;
case 'future' :
$stat = __('Scheduled');
break;
case 'pending' :
$stat = __('Pending Review');
break;
case 'draft' :
$stat = __('Draft');
break;
}
if ( '0000-00-00 00:00:00' == $post->post_date ) {
$time = '';
} else {
/* translators: date format in table columns, see http://php.net/date */
$time = mysql2date(__('Y/m/d'), $post->post_date);
}
$html .= '<tr class="found-posts"><td class="found-radio"><input type="radio" id="found-'.$post->ID.'" name="found_post_id" value="' . esc_attr($post->ID) . '"></td>';
$html .= '<td><label for="found-'.$post->ID.'">' . esc_html( $title ) . '</label></td><td class="no-break">' . esc_html( $post_types[$post->post_type]->labels->singular_name ) . '</td><td class="no-break">'.esc_html( $time ) . '</td><td class="no-break">' . esc_html( $stat ). ' </td></tr>' . "\n\n";
}
$html .= '</tbody></table>';
$x = new WP_Ajax_Response();
$x->add( array(
'data' => $html
));
$x->send();
}
Javascript file open-posts.js
jQuery(document).ready(function($) {
// Find posts
var $findBox = $('#find-posts'),
$found = $('#find-posts-response'),
$findBoxSubmit = $('#find-posts-submit');
// Open
$('input.kc-find-post').live('dblclick', function() {
$findBox.data('kcTarget', $(this));
findPosts.open();
});
// Insert
$findBoxSubmit.click(function(e) {
e.preventDefault();
// Be nice!
if ( !$findBox.data('kcTarget') )
return;
var $selected = $found.find('input:checked');
if ( !$selected.length )
return false;
var $target = $findBox.data('kcTarget'),
current = $target.val(),
current = current === '' ? [] : current.split(','),
newID = $selected.val();
if ( $.inArray(newID, current) < 0 ) {
current.push(newID);
$target.val( current.join(',') );
}
});
// Double click on the radios
$('input[name="found_post_id"]', $findBox).live('dblclick', function() {
$findBoxSubmit.trigger('click');
});
// Close
$( '#find-posts-close' ).click(function() {
$findBox.removeData('kcTarget');
});
});