Skip to content

Commit

Permalink
Issue #167: curl step
Browse files Browse the repository at this point in the history
  • Loading branch information
marcghaly committed Jul 7, 2022
1 parent d79f4da commit b489a1f
Show file tree
Hide file tree
Showing 4 changed files with 383 additions and 4 deletions.
160 changes: 158 additions & 2 deletions classes/local/step/connector_curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,171 @@ class connector_curl extends connector_step {
/** @var bool whether or not this step type (potentially) contains a side effect or not */
protected $hassideeffect = true;

/** @var int Time after which curl request is aborted */
protected $timeout = 60;

/**
* Return the definition of the fields available in this form.
*
* @return array
*/
protected static function form_define_fields(): array {
return [
'curl' => ['type' => PARAM_URL],
'destination' => ['type' => PARAM_PATH],
'headers' => ['type' => PARAM_RAW],
'method' => ['type' => PARAM_TEXT],
'rawpostdata' => ['type' => PARAM_RAW],
'sideeffects' => ['type' => PARAM_RAW],
'timeout' => ['type' => PARAM_INT],
];
}

/**
* Allows each step type to determine a list of optional/required form
* inputs for their configuration
*
* It's recommended you prefix the additional config related fields to avoid
* conflicts with any existing fields.
*
* @param \MoodleQuickForm &$mform
*/
public function form_add_custom_inputs(\MoodleQuickForm &$mform) {
$mform->addElement('text', 'config_curl', get_string('connector_curl:curl', 'tool_dataflows'),
['cols' => 50, 'rows' => 7]);
$mform->addElement('text', 'config_destination', get_string('connector_curl:destination', 'tool_dataflows'));
$mform->addHelpButton('config_destination', 'connector_curl:destination', 'tool_dataflows');
$mform->addElement('textarea', 'config_headers', get_string('connector_curl:headers', 'tool_dataflows'),
['cols' => 50, 'rows' => 7]);
$mform->addElement('static', 'headers_help', '', get_string('field_headers_help', 'tool_dataflows'));
$mform->addElement('select', 'config_method', get_string('connector_curl:method', 'tool_dataflows'),
['get' => 'GET', 'post' => 'POST', 'put' => 'PUT']);
$mform->addElement('textarea' , 'config_rawpostdata', get_string('connector_curl:rawpostdata', 'tool_dataflows'),
['cols' => 50, 'rows' => 7]);
$mform->addElement('checkbox', 'config_sideeffects', get_string('connector_curl:sideeffects', 'tool_dataflows'),
get_string('yes'));
$mform->hideIf('config_rawpostdata', 'config_method', 'eq', 'get');
$mform->disabledIf('config_rawpostdata', 'config_method', 'eq', 'get');
$mform->addElement('text', 'config_timeout', get_string('connector_curl:timeout', 'tool_dataflows'));
$mform->addHelpButton('config_timeout', 'connector_curl:timeout', 'tool_dataflows');
}

/**
* Validate the configuration settings.
*
* @param object $config
* @return true|\lang_string[] true if valid, an array of errors otherwise
*/
public function validate_config($config) {
$errors = [];
if (empty($config->curl)) {
$errors['config_curl'] = get_string('config_field_missing', 'tool_dataflows', 'curl', true);
}
if (empty($config->rawpostdata) && ($config->method === 'put' || $config->method === 'post')) {
$errors['config_rawpostdata'] = get_string('config_field_missing', 'tool_dataflows', 'rawpostdata', true);
}
return empty($errors) ? true : $errors;
}

/**
* Executes the step
*
* TODO: This will perform a curl call
* Performs a curl call according to given parameters.
*
* @return bool Returns true if successful, false otherwise.
*/
public function execute(): bool {
// TODO: Implement.
// Get variables.
$config = $this->enginestep->stepdef->config;
$isdryrun = $this->enginestep->engine->isdryrun;
$method = $config->method;
$dbgcommand = 'curl -X ' . strtoupper($method) . ' ' . $config->curl;
$result = null;

if (!empty($config->timeout)) {
$this->timeout = (int) $config->timeout;
$dbgcommand .= ' --max-time ' . $config->timeout;
}

$options = ['CURLOPT_TIMEOUT' => $this->timeout];

if ($method === 'post') {
$options['CURLOPT_POST'] = 1;
}

if ($method === 'put') {
$options['CURLOPT_PUT'] = 1;
}

$curl = new \curl();

$this->hassideeffect = !empty($config->sideeffects);

// For put and post methods automatically overrides/has side effects.
if ($config->method != 'get') {
$this->hassideeffect = true;
}

// Provided a header is specified add header to request.
if (!empty($config->headers)) {
$headers = $config->headers;
if (!is_array($headers)) {
$headers = json_decode($headers, true);
}
$curl->setHeader($headers);
$dbgcommand .= ' -H \'' . $config->headers . '\'';
}

// Sets post data.
if (!empty($config->rawpostdata)) {
$options['CURLOPT_POSTFIELDS'] = $config->rawpostdata;
$dbgcommand .= ' -d \'' . $config->rawpostdata . '\'';
}

// Download response to file provided destination is set.
if (!empty($config->destination) && !$isdryrun) {
if ($config->destination[0] === '/') {
$config->destination = ltrim($config->destination, '/');
}
$config->destination = $this->enginestep->engine->resolve_path($config->destination);
$file = fopen($config->destination, 'w');
$options['CURLOPT_FILE'] = $file;
}

// Perform call.
if (!$isdryrun) {
$result = $curl->$method($config->curl, [], $options);
}

if (!empty($file)) {
fclose($file);
}

$info = $curl->get_info();
// Stores response to be reusable by other steps.
// TODO : Once set_var api is refactored add response.
$response = $curl->getResponse();
$httpcode = $info['http_code'] ?? null;
$connecttime = $info['connect_time'] ?? null;
$totaltime = $info['total_time'] ?? null;
$sizeupload = $info['size_upload'] ?? null;
$destination = !empty($config->destination) ? $config->destination : null;
$errno = $curl->get_errno();

if (($httpcode >= 400 || empty($response) || $errno == 28) && !$isdryrun) {
throw new \moodle_exception($httpcode . ':' . $result);
}

if (!$isdryrun) {
$this->enginestep->stepdef->set_var('result', $result);
$this->enginestep->stepdef->set_var('httpcode', $httpcode);
$this->enginestep->stepdef->set_var('connecttime', $connecttime);
$this->enginestep->stepdef->set_var('totaltime', $totaltime);
$this->enginestep->stepdef->set_var('sizeupload', $sizeupload);
$this->enginestep->stepdef->set_var('destination', $destination);
} else {
$this->enginestep->stepdef->set_var('dbgcommand', $dbgcommand);
}
return true;
}
}
13 changes: 13 additions & 0 deletions lang/en/tool_dataflows.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,16 @@
$string['trigger_cron:crontab_desc'] = 'The schedule is edited as five values: minute, hour, day, month and day of month, in that order. The values are in crontab format.';
$string['trigger_cron:strftime_datetime'] = '%d %b %Y, %H:%M';
$string['trigger_cron:next_run_time'] = 'Next run time: {$a}';

// Connector cURL.
$string['connector_curl:curl'] = 'Client URL';
$string['connector_curl:destination'] = 'File / Response destination';
$string['connector_curl:destination_help'] = 'Should this field be left blank,
response will be stored in step definition to be reused subsequently by other steps.';
$string['connector_curl:headers'] = 'Headers';
$string['connector_curl:method'] = 'HTTP request method';
$string['connector_curl:rawpostdata'] = 'Raw post data';
$string['connector_curl:sideeffects'] = 'Does this request have side effects?';
$string['connector_curl:timeout'] = 'Timeout';
$string['connector_curl:timeout_help'] = 'Time after which curl request will abort, default is 60 seconds.';
$string['field_headers_help'] = 'Headers should be in the following JSON format: <br> {<br>"header-key": "header value", <br>"another-key": "1234"<br>}';
Loading

0 comments on commit b489a1f

Please sign in to comment.