1

I have a form in my site (custom module) where the user chooses some options and depending on those options, some code is executed.

The code basically executes shell commands on a remote server and at the moment, all the drupal_set_message commands are only shown after all commands are executed.

Is there any way that I can show those messages as soon as each part of the code is executed?

function mymodule_connect() {
    $host = '<my ip address>';
    $username = '<my username>';
    $password = '<my password>';

    $connection = ssh2_connect($host, 22);
    ssh2_auth_password($connection, $username, $password);

    return $connection;
}

function mymodule_form_submit(&$form, &$form_state) {

    $delete_load = $form_state['values']['delete_load'];
    $change_load = $form_state['values']['change_load'];
    $load_number = $form_state['values']['check_load'];


    if($change_load == 1) {
        # DELETE
        # perform some command

        # LOAD
        # perform other command

        drupal_set_message('Deleting. Please wait...', 'error');
        drupal_set_message('Changing load. Please wait...', 'warning');
    } elseif ($delete_load == 1 && $change_load == 0) {
        # DELETE    
        drupal_set_message('Deleting. Please wait...', 'error');

        $command = "<some shell script command to send>";

        $connection = mymodule_connect();

        $stream = ssh2_exec($connection, $command);
        stream_set_blocking($stream, true);
        $output = stream_get_contents($stream);
        dpm($output);

        ssh2_exec($connection, 'exit');
        unset($connection);

    } else {
        # do nothing
    }

    # OTHER DIFFERENT SET OF COMMANDS TO SEND
    drupal_set_message('Configuring. Please wait...', 'status');

    $commands = array(
        "<some shell script command to send>",
        "<some shell script command to send>",
        "<some shell script command to send>",
    );

    $connection = mymodule_connect();

    foreach ($commands as $command) {
        $stream = ssh2_exec($connection, $command);
        stream_set_blocking($stream, true);
        $output = stream_get_contents($stream);
        dpm($output);
    };

    ssh2_exec($connection, 'exit');
    unset($connection);

    return t('Success');
}
3
  • Drupal 7 I assume?
    – rooby
    Oct 9, 2017 at 10:26
  • Javascript & ajax is the answer, but I'm sure someone can get more specific than that.
    – rooby
    Oct 9, 2017 at 10:29
  • Sure. Is Drupal 7
    – zephirus
    Oct 9, 2017 at 12:31

2 Answers 2

1
+100

As Mohammed Gomma mentioned you need to use Batch API to achieve the same. The implementation of batch api may look something like code given below:

  1. Create Batch array
  2. Create function to execute commands and call them using batch operation
  3. set context message in the function created
  4. create finish callback

    function mymodule_form_submit(&$form, &$form_state) {

          $delete_load = $form_state['values']['delete_load'];
          $change_load = $form_state['values']['change_load'];
          $load_number = $form_state['values']['check_load'];
    
    
          if ($change_load == 1) {
            # DELETE
            # perform some command
            # LOAD
            # perform other command
    
            drupal_set_message('Deleting. Please wait...', 'error');
            drupal_set_message('Changing load. Please wait...', 'warning');
          }
          elseif ($delete_load == 1 && $change_load == 0) {
            # DELETE    
            drupal_set_message('Deleting. Please wait...', 'error');
    
            $command = "<some shell script command to send>";
    
    
    
            $stream = ssh2_exec($connection, $command);
            stream_set_blocking($stream, true);
            $output = stream_get_contents($stream);
            dpm($output);
    
            ssh2_exec($connection, 'exit');
            unset($connection);
          }
          else {
            # do nothing
          }
    
          # OTHER DIFFERENT SET OF COMMANDS TO SEND
          drupal_set_message('Configuring. Please wait...', 'status');
    
          $commands = array(
            "<some shell script command to send>",
            "<some shell script command to send>",
            "<some shell script command to send>",
          );
    
          $batch = array(
            'finished' => 'batch_example_finished',
            'title' => t('Processing Example Batch'),
            'init_message' => t('Example Batch is starting.'),
            'progress_message' => t('Processed @current out of @total.'),
            'error_message' => t('Example Batch has encountered an error.'),
          );
          foreach ($commands as $command) {
            $batch['operations'][] = array('_batch_execute_command', array($command));
          };
    
          batch_set($batch);
    
    
    
          return t('Success');
        }
    
        function _batch_execute_command($command, &$context) {
          $connection = mymodule_connect();
          $stream = ssh2_exec($connection, $command);
          stream_set_blocking($stream, true);
          $output = stream_get_contents($stream);
          dpm($output);
          ssh2_exec($connection, 'exit');              
          unset($connection);
          $context['message'] = t('Now processing %command', array('%command' => $command));
          if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
            $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
          }
        }
    
        /**
         * Batch 'finished' callback
         */
        function batch_example_finished($success, $results, $operations) {
          if ($success) {
            // Here we do something meaningful with the results.
            $message = t('@count items successfully processed:', array('@count' => count($results)));
            // $message .= theme('item_list', $results);  // D6 syntax
            $message .= theme('item_list', array('items' => $results));
            drupal_set_message($message);
          }
          else {
            // An error occurred.
            // $operations contains the operations that remained unprocessed.
            $error_operation = reset($operations);
            $message = t('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)));
            drupal_set_message($message, 'error');
          }
        }
    

Nested batch example:

function gamify_batch_form($form, $form_state) {
  return array(
    'submit' => array(
      '#type' => 'submit',
      '#title' => 'submit',
      '#value' => 'submit',
    ),
  );
}

function gamify_batch_form_submit($form, $form_state) {
  $batch = array(
    'finished' => 'batch_example_finished',
    'title' => t('Processing Example Batch'),
    'init_message' => t('Example Batch is starting.'),
    'progress_message' => t('Processed @current out of @total.'),
    'error_message' => t('Example Batch has encountered an error.'),
  );
  $commands = array(
    'hi',
    'hello',
    'world',
    'dear',
    'world',
  );
  foreach ($commands as $command) {
    $batch['operations'][] = array('_batch_execute_command', array($command));
  };

  batch_set($batch);
}

function _batch_execute_command($command, &$context) {
  file_get_contents("https://drupal.stackexchange.com/questions/246767/show-progress-of-code-executed-on-form-submit/247718?noredirect=1#comment303466_247718");
  if($command == 'world'){
    $batch = array(
      'finished' => 'batch_example_finished',
      'title' => t('Processing Example Batch'),
      'init_message' => t('Example Batch is starting.'),
      'progress_message' => t('Processed @current out of @total.'),
      'error_message' => t('Example Batch has encountered an error.'),
    );
    $commands = array(
      'hi 1',
      'hello 2',
      'dear 3',
    );
    foreach ($commands as $innercommand) {
      $batch['operations'][] = array('_batch_execute_command', array($innercommand));
    };

    batch_set($batch);
  }
  $context['message'] = t('Now processing %command', array('%command' => $command));
  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
}

/**
 * Batch 'finished' callback
 */
function batch_example_finished($success, $results, $operations) {
  if ($success) {
    // Here we do something meaningful with the results.
    $message = t('@count items successfully processed:', array('@count' => count($results)));
    // $message .= theme('item_list', $results);  // D6 syntax
    $message .= theme('item_list', array('items' => $results));
    drupal_set_message($message);
  }
  else {
    // An error occurred.
    // $operations contains the operations that remained unprocessed.
    $error_operation = reset($operations);
    $message = t('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)));
    drupal_set_message($message, 'error');
  }
}
7
  • Thanks. @Vishal Patil: When you call _batch_execute_command, I think you don't need the $stream. I removed it but I still have an issue. The variable $connection is not passed to the function.
    – zephirus
    Oct 10, 2017 at 13:46
  • You can create connection inside _batch_execute_command() instead of passing the argument. Please check updated sample code. Oct 10, 2017 at 17:17
  • Thanks Vishal Patil. Now is working. I am missing just one thing. If I add a call to a new function inside "foreach ($commands as $command) {", can I add it to the batch? The thing is: for each command, if some variable is equal to something, I will have another set of commands that I need to run in a batch.
    – zephirus
    Oct 11, 2017 at 10:13
  • EDIT: noticed now that for that new batch, I need a value that is generated inside the first batch. So this seems a case that I need batches inside batches. Don't know if this is the best way to do it
    – zephirus
    Oct 11, 2017 at 10:30
  • You can try creating nested batch inside _batch_execute_command() function if the condition is matched. Check answers for nested batch example. Oct 11, 2017 at 11:18
4

Surely you need to implement Batch API to achieve what you need, and keep the user notified for what's happening in the background.

Very similar question that will help you more. Read it carefully.

These modules will help you:

  1. Progerss This is an attempt to implement a generic framework for keeping track of any progress
  2. Background progress takes over the existing Batch API and runs batch jobs in a background process

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.