How can I achieve server sent events in WordPress. I have searched a lot about it but not able to figure out how can I implement it. The problem I am facing is SSE only takes header as 'Content-Type:text/plain'. How can I sent some event to js script only when some changes occur in my database through WordPress function?
If I use 'Content-Type:text/plain' in WordPress function whole website will turn into text output.
I am aware that Wordpress has heartbeat API but I don't want that. Currently, I am using ajax pooling to achieve the same task but it is taking a lot of resources.
you can make a custom REST API end point and make something like that:
public function prepare_rest_api_demo_response( $request ){
$args = array(
'numberposts' => 1
);
$last_id = '';
$serverTime = time();
$latest_posts = get_posts( $args );
$last_id = $latest_posts['0']->ID;
do {
$latest_posts = get_posts( $args );
if( $latest_posts['0']->ID > $last_id ){
$response = new WP_REST_Response($this->compile_sendMsg_in_apiendpoint_for_SSE($serverTime,$latest_posts,$last_id), 200);
break;
}
// break the loop if the client aborted the connection (closed the page)
if ( connection_aborted() ) {
$response = new WP_REST_Response('data: {connection: "closed"}'."\n\n", 503);
break;
}
sleep(2);
// If we didn't use a while loop, the browser would essentially do polling
// every ~3seconds. Using the while, we keep the connection open and only make
// one request.
} while(true);
$response->header( 'Content-Type', 'text/event-stream' );
$response->header( 'Cache-Control', 'no-cache' );
//$response->header( 'Access-Control-Allow-Origin', '*' );
$response->header( 'Accept', 'application/json' );
return $response;
}
and this function for compile the response in the correct way
private function compile_sendMsg_in_apiendpoint_for_SSE($id, $msg, $lastitemid) {
$msg = json_encode($msg);
echo "id: $id" . "\n";
echo "retry: 1500" . "\n";
//echo "event: nameofevent" . "\n"; // if you need some name for event uncomment that line
echo "data: $msg". PHP_EOL;//use \n instead of PHP_EOL for add another line data: if is the same data object https://www.html5rocks.com/en/tutorials/eventsource/basics/#toc-js-api
echo PHP_EOL;
ob_flush();
flush();
}
Related
-- Edit 1 --
Found out some new things. I'm adding them on top since they might be more relevant than the code below.
I've rerun the scripts a few times. I now get different findings actually.
Running var_dump($wp_query->query); right after $the_query = new WP_Query($queryArgs);In the first render of the post loop gives me the query vars of the page the loop is rendered on. Calling it with ajax it reruns the same part of the code, right? So than it returns empty.
My thoughts:
Pages is called, runs funtions.php.
Runs the part of the wp_enqueue_script('rtt_scripts');
This is the moment it gets the current $wp_query values. Which are the values of the page.
Than renders the page with the post loop.
This is the moment the post loop runs $the_query = new WP_Query($queryArgs);
On press of the load more the ajax than calls it to rerun the post loop. With the query vars set with wp_enqueue_script('rtt_scripts');
This made me think. Am I running the code in a wrong order? Are the query vars for ajax set on the wrong moment? Other thought. Should I focus on how to get the query vars on the first post loop to the ajax query vars?
-- End Edit --
I’m having trouble with a load more button in Wordpress. The code below is the basic code I have right now.
As far as I can see this should be a working code :) Problem is though that this doesn’t work.
My problem is that I don’t know where to start debugging. Closest I know where the problem lies is this:
In rtt_loadmore_ajax_handler() there is the var $queryArg
When var_dumping the var $queryArg in both rtt_post_grid() and rtt_loadmore_ajax_handler()
It gives different results. Here I would expect the same results. In the Ajax call it returns the arguments
of the current rendered page and not of the post query on this page.
Would the global $wp_query; be the problem? And how do I go from here?
The basic post code:
function rtt_post_grid()
{
$queryArgs = Array(
"post_type" => Array(
'news',
'agenda'
),
'posts_per_page' => 4,
'post_status' => 'publish',
'paged' => 1
);
// post grid wrap
echo '<div id="rtt_posts_wrap" >';
rtt_post_grid_query($queryArgs);
echo '</div>';
// load more button
echo '<form>';
echo '<button id="rtt_loadmore" class=" button">Load more post</button> ';
echo '<input type="hidden" name="action" value="loadmore" />'; // this line might be obsolete
echo '</form>';
}
function rtt_post_grid_query($queryArgs)
{
// The Query
$the_query = new WP_Query($queryArgs);
// The Loop
if ($the_query->have_posts()) {
echo '<ul>';
while ($the_query->have_posts()) {
$the_query->the_post();
echo '<li>' . get_the_title() . '</li>';
}
echo '</ul>';
/* Restore original Post Data */
wp_reset_postdata();
} else {
// no posts found
echo 'no posts found';
}
}
Setting the JS:
if(!has_action('rtt_post_grid_script_and_styles')) {
add_action('wp_enqueue_scripts', 'rtt_post_grid_script_and_styles', 1);
function rtt_post_grid_script_and_styles()
{
global $wp_query;
wp_register_script('rtt_scripts', plugin_dir_url( __FILE__ ) . 'js/script.js', array('jquery'), time());
wp_enqueue_script('rtt_scripts');
wp_localize_script('rtt_scripts', 'rtt_loadmore_params', array(
'ajaxurl' => site_url() . '/wp-admin/admin-ajax.php', // WordPress AJAX
'posts' => json_encode($wp_query->query_vars), // everything about your loop is here
'current_page' => $wp_query->query_vars['paged'] ? $wp_query->query_vars['paged'] : 1,
'max_page' => $wp_query->max_num_pages
));
wp_enqueue_script('rtt_scripts');
}
}
The JS/Ajax:
jQuery(function($){
$(window).ready(function() {
$('#rtt_loadmore').click(function () {
$.ajax({
url: rtt_loadmore_params.ajaxurl,
data: {
'action': 'loadmore', // the parameter for admin-ajax.php
'query': rtt_loadmore_params.posts, // loop parameters passed by wp_localize_script()
'page': rtt_loadmore_params.current_page, // current page
},
dataType: 'json',
type: 'POST',
beforeSend: function (xhr) {
$('#rtt_loadmore').text('Bezig met laden...'); // some type of preloader
},
success: function (data) {
if (data) {
$('#rtt_loadmore').text('More posts');
$('#rtt_posts_wrap').append(data.content); // insert new posts
rtt_loadmore_params.current_page++;
if (rtt_loadmore_params.current_page == rtt_loadmore_params.max_page){
$('#rtt_loadmore').hide(); // if last page, HIDE the button
}
} else {
$('#rtt_loadmore').hide(); // if no data, HIDE the button as well
}
}
});
return false;
});
});
});
The Ajax handler:
add_action('wp_ajax_loadmore', 'rtt_loadmore_ajax_handler'); // wp_ajax_{action}
add_action('wp_ajax_nopriv_loadmore', 'rtt_loadmore_ajax_handler'); // wp_ajax_nopriv_{action}
function rtt_loadmore_ajax_handler(){
$postData = $_POST;
// prepare our arguments for the query
$queryArgs = json_decode( stripslashes( $postData['query'] ), true );
$queryArgs['paged'] = $postData['page'] + 1; // we need next page to be loaded
$queryArgs['post_status'] = 'publish';
ob_start();
rtt_post_grid_query($queryArgs);
$output = ob_get_contents();
ob_end_clean();
global $the_query;
echo json_encode( array(
'posts' => serialize( $the_query->query_vars ),
'max_page' => $the_query->max_num_pages,
'found_posts' => $the_query->found_posts,
'content' => $output
) );
die;
}
So, I've figured it out. I'll explain for it might be useful to somebody else.
The reason it did not work is because the code above is more useful in a template. But I use it in a shortcode. The wp_localize_script() was run on rendering the page and not on running the shortcode. That's why it didn't had the right variables.
I've moved the code below inside the shortcode. Right after the new WP_query:
// The Query
$the_query = new WP_Query($queryArgs);
// The Loop
if ($the_query->have_posts()) {
wp_enqueue_script_ajax_vars($the_query);
Than passed the new query
function wp_enqueue_script_ajax_vars($the_query)
{
wp_register_script('rtt_scripts', plugin_dir_url(__FILE__) . 'js/script.js', array('jquery'), time());
wp_localize_script('rtt_scripts', 'rtt_loadmore_params', array(
'ajaxurl' => site_url() . '/wp-admin/admin-ajax.php', // WordPress AJAX
'posts' => json_encode($the_query->query_vars), // everything about your loop is here
'query_vars' => json_encode($the_query->query),
'current_page' => $the_query->query_vars['paged'] ? $the_query->query_vars['paged'] : 1,
'max_page' => $the_query->max_num_pages,
));
wp_enqueue_script('rtt_scripts', '', '', '', true); // note the last 'true' this sets it inside the footer
}
Resulting in wp_localize_script() creating the variable in the footer. It was in the header before. But by getting it within the shortcode, sending the new query arguments and putting them inside the footer (since the header is already rendered by then) I have set the JS var for Ajax.
Add the two order arguments to $queryArgs.
// prepare our arguments for the query
$queryArgs = json_decode( stripslashes( $postData['query'] ), true );
$queryArgs['paged'] = $postData['page'] + 1; // we need next page to be loaded
$queryArgs['post_status'] = 'publish';
$queryArgs['orderby'] = 'date'; // add this to order by date
$queryArgs['order'] = 'DESC'; // add this to display the most recent
I am trying to update a specific element in my HTML document using server-sent events. I am using the W3Schools example with parameters changed to fit my needs. When this did not work out of the box, I tried this question's solution, which is to include charset=utf-8 in my Content-Type header. Still no updates to the HTML. I then realized I was getting the following error:
EventSource's response has a MIME type ("text/html") that is not "text/event-stream". Aborting the connection.
Here is the relevant JavaScript in my .js file:
var source = new EventSource("warehouse.php");
source.onmessage = function(event) {
document.getElementById("processing").innerHTML += event.data + "<br>";
};
In my PHP file, I have the following:
if (isset($args->products)) {
orderProducts($args->products);
}
function orderProducts($products) {
$bigResponse = outgoingData(constant('bigURL'), $products[0]);
$littleResponse = outgoingData(constant('lilURL'), $products[3]);
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache');
echo "data: Order has been placed.";
flush();
}
I am completely stumped. What am I missing? How do I fix this error?
Try sending a json encoded data part/element back to the browser.
Lets see if I can remember correctly... It's out of my mind, so it isn't tested!
PHP Side:
// Set SSE headers which are send along first output.
header('Content-Type: text/event-stream; charset=UTF-8');
header('Cache-Control: no-cache');
$sseId = 0; //Increment this value on each next message
$event = 'eventName'; //Name of the event which triggers same named event-handler in js.
//$data needs to be json encoded. It can be a value of any type.
$data = array ('status' => 'Progress', 'message' => 'My message or html');
//Send SSE Message to browser
echo 'id: ' . $sseId++ . PHP_EOL; //Id of message
if (!is_null($event)) {
echo 'event: ' . $event . PHP_EOL; //Event Name to trigger eventhandler
}
retry: 10000 . PHP_EOL; //Define custom reconnection time. (Default to 3s when not specified)
echo 'data: ' . json_encode($data) . PHP_EOL; //Data to send to eventhandler
//Note: When sending html, you might need to encode with flags: JSON_HEX_QUOT | JSON_HEX_TAG
echo PHP_EOL;
ob_flush();
flush();
JavaScript side:
var es = new EventSource('sse.php');
es.addEventListener('eventName', function(event) {
var returnData = JSON.parse(event.data);
var myElement = document.getElementById("processing");
switch (returnData.status) {
case 'Error':
es.close();
myElement.innerHTML = 'An error occured!';
break;
case 'Done':
es.close();
myElement.innerHTML = 'Finished!';
break;
case 'Progress':
myElement.innerHTML += returnData.message + "<br>";
break;
}
});
es.onerror = function() {
console.log("EventSource failed.");
};
In this case I make use of 1 (named) event only where the data element contains a status on which js reacts.
You can also define multiple event-names to be used as a status and define corresponding event-handlers or define a handler which is triggered on each message received.
es.onmessage = function(event) {
//This handler fires on each message received.
console.log("Incoming message.");
};
If you have server-side script running longer then 3s, specify a longer retry time in the message sent (See code at PHP side). Otherwise the browser will reconnect after 3 seconds which might run your script from the beginning and abandon the previous run.
I have a small problem. On my wordpress based website I want to create an Order-Status form where people can use a code and check their order's progress. It's not an online store so I don't use woocommerce. The file containing the order's progress is a CSV file.
I tried to use that through a function but didn't work. I even tried Javascript but my code can't find the file on server :(
My question is: What language and what technique should I use for my need.
Thank a lot guyz.
I think this is what you are looking for without the need of any libraries:
First You create a form (the form action can be your homepage since we will be listening for the $_GET parameters on init, which is run on every page load):
<form action="<?php echo site_url() ?>">
<input type="hidden" name="csv_export" value="order_status" />
<input type="text" name="code" />
<input type="submit" value="Download Report" name="download_report" />
</form>
Than you need to add an action on init in functions.php to listen for the get parameter csv_export in order to start your functionality to prepare the csv file and output it for download: (we are using the exit(); function after we create csv to make sure that nothing else runs after this process.)
function check_for_export() {
if ( isset( $_GET['csv_export'], $_GET['code'] ) && $_GET['csv_export'] == 'order_status' ) {
ob_end_clean();
export_order_status_csv( $_GET['code'] );
exit();
}
}
}
add_action('init', 'check_for_export');
Now you can start the functionality to generate the csv report. This function depends on how you are fetching the data but you can follow this example to set the headers and output the report:
function export_order_status_csv( $code ) {
// Make a DateTime object and get a time stamp for the filename
$date = new DateTime();
$ts = $date->format( "Y-m-d-G-i-s" );
// A name with a time stamp, to avoid duplicate filenames
$filename = "order-statuses-export-$ts.csv";
// Tells the browser to expect a CSV file and bring up the
// save dialog in the browser
header( 'Pragma: public' );
header( 'Expires: 0' );
header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
header( 'Content-Description: File Transfer' );
header( 'Content-Type: text/csv' );
header( 'Content-Disposition: attachment;filename=' . $filename );
header( 'Content-Transfer-Encoding: binary' );
// This opens up the output buffer as a "file"
$fp = fopen( 'php://output', 'w' );
//This needs to be customised from your end, I am doing a WP_Query for this example
$results = new WP_Query();
if ( $results->have_posts() ) {
//This is set to avoid issues with special characters
$bom = ( chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF ) );
//add BOM to fix UTF-8 in Excel
fputs( $fp, $bom );
// Set the headers of the csv
fputcsv( $fp, [
'orderID',
'orderDate',
'orderTotal'
] );
while ( $results->have_posts() ) {
$results->the_post();
//Here we are inserting the row data per result fetched
fputcsv(
$fp,
[
get_the_ID(),
get_the_date(),
'your_custom_data'
]
);
}
wp_reset_query();
// Close the output buffer (Like you would a file)
fclose( $fp );
} else {
fputcsv( $fp, [ 'No Results' ] );
fclose( $fp );
}
}
When exporting a csv you have to hook to an action which is processed before anything has been outputted. There cannot be any output before creating the csv file since we are updating the headers.
If you want to read the data from a csv file and manipulate it to customise your csv export you can use this function instead of the WP_Query in example above: http://php.net/manual/en/function.fgetcsv.php
$row = 1;
if (($handle = fopen("test.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
echo "<p> $num fields in line $row: <br /></p>\n";
$row++;
for ($c=0; $c < $num; $c++) {
echo $data[$c] . "<br />\n";
}
}
fclose($handle);
}
I actually used PHPExcel in a previous project that achieved what you're after, though I see it's been deprecated in favour of PhpSpreadsheet library. So I would use that one!
What both libraries can essentially do (amongst many other things) is parse the spreadsheet and return the relevant information based on what you request.
So you could put your spreadsheet in a separate directory which you then use the PhpSpreadsheet library to extract information from - and present to the customer in whatever way you see fit.
I have a simple AJAX function bound to a button that should execute a PostgreSQL query. However, when I click the button that I bound the ajax query to, all I get is the confirmation that the database connection was successful. Nothing seems to happen withe the ajax result (should be printing to console in the handleAjax() function. What am I doing wrong?
This is the javascript code (with jquery):
$(document).ready(function() {
function sendAjax() {
$.ajax({
url: "db/database.php",
success: function (result) {
handleAjax(result);
}
});
}
function handleAjax(result) {
console.log(result);
}
$("#submit-button").on("click", sendAjax);
});
And this it the contents of database.php:
<?php
function dbconn(){
ini_set('display_errors', 1); // Displays errors
//database login info
$host = 'localhost';
$port = 5432;
$dbname = 'sms';
$user = 'postgres';
$password = 'postgres';
// establish connection
$conn = pg_connect("host=$host port=$port dbname=$dbname user=$user password=$password");
if (!$conn) {
echo "Not connected : " . pg_error();
exit;
} else {
echo "Connected.";
}
}
$conn = dbconn();
$sql = "SELECT * FROM numbers;";
$result = pg_query( $sql ) or die('Query Failed: ' .pg_last_error());
$count = 0;
$text = 'error';
while( $row = pg_fetch_array( $result, null, PGSQL_ASSOC ) ) {
$text = $row['message'];
//echo $text;
}
pg_free_result( $result );
?>
The problem is in the database.php file, all you get is "Connected." because you don't print your result at the end. Ajax only receive the output of the php file.
So at the end of your php file you should add :
echo $text;
And you also should remove the echo "Connected.";
AJAX is not a magic wand that in magic way reads PHP code. Let's say AJAX is a user. So what does user do.
Open web page
Wait until PHP execute code and display data
Tells you what he sees
If you don't display anything, ajax can't tell you what he saw.
In thi's place is worth to say that the best way to communicate between PHP and AJAX is using JSON format.
Your code generally is good. All you have to do is to display your data. All your data is in your $text var. So let's convert your array ($text) to JSON.
header('Content-Type: application/json');
echo json_encode($text);
First you set content-type to json, so ajax knows that he reads json. Then you encode (convert) your PHP array to js-friendly format (JSON). Also delete unnecessary echoes like 'Conntected' because as I said, AJAX reads everything what he sees.
You should return $conn from dbconn()
if (!$conn) {
echo "Not connected : " . pg_error();
exit;
} else {
echo "Connected.";
return $conn;
}
I am using HTML5 Server-Sent Events.
Actually I need to show notification (new record enter and which are unread) that's when any new record is insert in database (php/mysql).
So for testing purpose I just tried with count of total row. But I am getting this error message in my local-host:
Firefox can't establish a connection to the server at http://localhost/project/folder/servevent/demo_sse.php.
The line is:
var source = new EventSource("demo_sse.php");
I have tried this:
index.php
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("demo_sse.php");
source.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data;
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
<div id="result"></div>
demo_sse.php
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$db = mysql_connect("localhost", "root", ""); // your host, user, password
if(!$db) { echo mysql_error(); }
$select_db = mysql_select_db("testdatase"); // database name
if(!$select_db) { echo mysql_error(); }
$time = " SELECT count( id ) AS ct FROM `product` ";
$result = mysql_query($time);
$resa = mysql_fetch_assoc($result);
echo $resa['ct'];
flush();
?>
Please let me know what going wrong.
I know for notification we can use Ajax with some interval time, but I don't want such thing. As I have N number of records and which may slow my resources.
According to this,
There are several 'rules' that need to be met, and yours is lacking at this point:
Output the data to send (Always start with "data: ")
It is somehow like:
echo "data: {$resa['ct']}\n\n";
Setting a header to text/event-stream worked for me:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// Rest of PHP code
Please modify the following code snippet according to your requirements
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// infinite loop
while (1) {
// output the current timestamp; REPLACE WITH YOUR FUNCTIONALITY
$time = date('r');
echo "data: Server time: {$time}\n\n"; // 2 new line characters
ob_end_flush();
flush();
sleep(2); // wait for 2 seconds
}
?>
I tested this code snippet myself; it's working for me. If you have any query, let me know.