2

I have a PHP script which uploads a CSV temporarily. One page reload the CSV data is got from $_FILES and converted to a JSON array.

I then iterate through the CSV Rows using $.each.

For each row I am doing an AJAX call to a PHP function which sets some order tracking data and sends an email.

Due to email restrictions I want to add a delay between each loop iteration. However i have attempted to do this using a set time out in the JavaScript which didn't work and also attempted to add a PHP sleep function before the email gets sent.

Neither work, the emails still get sent at the same time with no delay.

It would appear all the requests I am making regardless of the delays I am adding are being processed at once.

How can I ensure the email sending is delayed?

jQuery ($csv_rows is the CSV data which was just uploaded)

<script>

    // Get CSV Rows into JSON array

    var csvRows = '<?php echo json_encode( $csv_rows ); ?>';
    var csvRows = ( jQuery.parseJSON( csvRows ) );

    // Loop through each row

    $.each( csvRows, function( key, value ) {

        // Split row into array exploded by comma

        row = value.toString().split( ',' );

        // Get column values

        order = row[0];
        courier = row[1];
        tracking = row[2];

        // AJAX

        var data = {
            'action': 'shd_tracking_import',
            'order': order,
            'courier': courier,
            'tracking': tracking,
        };

        // Do the ajax

        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: data,
            success: function( response ) {
                $( '#shd-import-results p' ).hide();
                if( response !== '0' ) {
                    $( '#shd-import-results ul' ).append( response );
                    importedCount = parseInt( $( '#shd-import-progress span' ).text() );
                    $( '#shd-import-progress span' ).text( importedCount + 1 );
                } else {
                    $( '<p>Error importing. Please ensure CSV meets requirements.</p>' ).appendTo( '#shd-import-results' );
                }
            }
        });

    });

</script>

PHP (this is the shd_tracking_import action referenced in AJAX)

if( isset( $_POST['order'] ) && isset( $_POST['courier'] ) && isset( $_POST['tracking'] ) ) {

    // Delay (due to their Office 365 limits)

    usleep( 4000000 ); // 4 Seconds (usleep used as sleep cannot contain fractions, usleep is microseconds, this was 2.5 seconds hence using usleep)

    // My mailing function is here (which works just not delayed)

    echo 'Done';

} else {

    echo '0';

}

exit;
6
  • 3
    That is a real odd way of doing it, would it not be easier to deal with the CSV file server side, and loop it there, just have a button in the js/frontend to activate it ? Additionally I understand how you are limited by Office 365 but I don't consider them an ESP (Email Service Provider), look into a proper ESP for mass emailing (with access to API's etc) Commented Dec 17, 2018 at 16:18
  • Better implement a job queue which process job after specified time Commented Dec 17, 2018 at 16:20
  • This should delay the emails. Try to delay for more than 4 seconds to notice the delay, 60 seconds may be good for testing. the receiver mail server may be queued the emails so you think they arrived at the same time Commented Dec 17, 2018 at 16:20
  • 1
    @Accountantمsort of, js is async so it does not wait for a reply from php to call the next row, so they are offset by 4 seconds, not 4 in between Commented Dec 17, 2018 at 16:22
  • @DarkMukke yes you are right , I thought the delay in the php is inside a loop sending the emails Commented Dec 17, 2018 at 16:26

4 Answers 4

1

You can use promises in jquery, when the API execution will finish then the promise code will execute.

See example

var div = $( "<div>" );

div.promise().done(function( arg1 ) {
  // Will fire right away and alert "true"
  alert( this === div && arg1 === div );
});
Sign up to request clarification or add additional context in comments.

3 Comments

that works to solve the actual problem, I think the question was asked wrong.
It should be done server side, but it does solve the problem the OP is facing.
I will give this a try.
1

While the answer by Piyush is correct, I would still do this server time. I understand why you would want to see the progress and there are a few options.

For example:

<?php

$file = tempnam(__DIR__, 'csv-upload');
$fh = fopen($file, 'a+b');
foreach ($csv_rows as $row) {
    fputcsv($fh, $row);
}
fclose($fh);
$rowCount = count($csv_rows);
$file = str_replace(__DIR__, '', $file);

?>
<script>
    var csvTotal = <?= $rowCount; ?>,
        csvCount = 0,
        end = false
    ;

    var looper = requestTimeout(function () {
        if (end || csvCount >= csvTotal) {
            clearTimeout(looper);
            return;
        }

        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {'file': '<?= $file; ?>'},
            success: function (response) {
                response = JSON.parse(response);
                if (!response.success) {
                    end = true;
                    $('<p>' + response.msg + '</p>').appendTo('#shd-import-results');
                } else {
                    csvCount = response.msg;
                    $('#shd-import-progress span').text(csvCount);
                }
            }
        });
    }, 4000);

</script>

and the ajax:

<?php

session_start();
ignore_user_abort(true);
set_time_limit(0);

//filename as unique key, so you can have mutiple queues at the same time
if (!isset($_POST['file'])) {
    echo json_encode([
        'success' => false,
        'msg' => 'File name required'
    ]);
    die();
}
$file = __DIR__ . $_POST['file'];
if (!file_exists($file)) {
    echo json_encode([
        'success' => false,
        'msg' => 'File does not excist'
    ]);
    die();
}


if (!isset($_SESSION['email_status'], $_SESSION['email_status'][$file])) {
    $fh = fopen($file, 'rb');
    $_SESSION['email_status'][$file] = 0;
    while ($row = fgetcsv($fh)) {
        $order = $row['order'];
        $courier = $row['courier'];
        $tracking = $row['tracking'];

        mail();
        $_SESSION['email_status'][$file]++;
        //4 seconds delaye
        usleep(4000);
    }
    fclose($fh);
    unlink($file);
} else {
    echo json_encode([
        'success' => false,
        'msg' => $_SESSION['email_status'][$file]
    ]);
}

With a session it will only start the email once. the problem here would be that when the session/cookies are cleared it will run it again, so it might be better to use a different storing mechanism (redis, or write a pid file or what not), but it should illustrate what I am trying to achieve.

Comments

1

Your script is running all instances at the same time, and all of those delay for 4 sec, but async.

Your delay should be in the JavaScript, to call the PHP file every 4 seconds.

Comments

0

After looking through the answers I decided to look at this a little differently as I didn't want to get into using sessions and I couldn't use .promise() on a $.each. So I rewrote the code to the following.

This works by getting the total rows from the CSV, running the AJAX once, I then use .done() on the ajax to rerun the AJAX call until it's got through all the rows and this works as I wanted.

<script>

    // Get CSV Rows into JSON array

    var csvRows = '<?php echo json_encode( $csv_rows ); ?>';
    var csvRows = ( jQuery.parseJSON( csvRows ) );
    var totalRows = csvRows.length - 1; // Minus 1 as starts 1, we need to start from 0 (e.g. for 4 rows this returns 4, when row 4 is actually 3 as starts from 0)

    // Do AJAX function

    function doAjax( $row ) {

        // Error

        var error = false;

        // Get row data

        order = csvRows[row][0];
        courier = csvRows[row][1];
        tracking = csvRows[row][2];

        // AJAX

        var data = {
            'action': 'shd_tracking_import',
            'order': order,
            'courier': courier,
            'tracking': tracking,
        };

        // Do the ajax

        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: data,
            success: function( response ) {
                $( '#shd-import-results p' ).hide();
                if( response !== '0' ) {
                    $( '#shd-import-results ul' ).append( response );
                    importedCount = parseInt( $( '#shd-import-progress span' ).text() );
                    $( '#shd-import-progress span' ).text( importedCount + 1 );
                } else {
                    error = true;
                }
            }
        }).done( function() {

            if( error !== true ) {

                // When AJAX is done

                var dt = new Date();
                var time = dt.getHours() + ":" + dt.getMinutes() + ":" + dt.getSeconds();

                // Row number total

                row = row + 1;

                // If row number is less than or equal to the total row count, if not do nothing, no further call.

                if( row <= totalRows ) {

                    console.log( 'Next AJAX about to run @ ' + time );
                    doAjax( row );

                } else {

                    $( '<span> - Import complete.</span>' ).appendTo( '#shd-import-progress' );

                }

            } else {

                $( '<span> - Import failed, check CSV meets requirements.</span>' ).appendTo( '#shd-import-progress' );

            }

        });

    }

    // Start row at zero

    row = 0;

    // Do Ajax (once row is done next row will run same AJAX function until all rows done)

    doAjax( row );

</script>

1 Comment

which is what @Piyush suggested, just without all your code, and you should still do it server side for so many reasons. If this is ran on a slow device, and the file is huge it will have massive implications. Also Sessions are not the only option, eg memcache, redis, (pid) file, mysql, sqlite, etc

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.