From b44544681d3f1d85a6e373c90cf95d95cf4e75f2 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 14 Dec 2021 16:34:56 -0800 Subject: [PATCH] Civi::pipe - Add some initial utilities --- Civi/Pipe/LineSessionTrait.php | 187 +++++++++++++++++++++++++++++++++ Civi/Pipe/PublicMethods.php | 105 ++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 Civi/Pipe/LineSessionTrait.php create mode 100644 Civi/Pipe/PublicMethods.php diff --git a/Civi/Pipe/LineSessionTrait.php b/Civi/Pipe/LineSessionTrait.php new file mode 100644 index 0000000000..f7ba82ef32 --- /dev/null +++ b/Civi/Pipe/LineSessionTrait.php @@ -0,0 +1,187 @@ +setIO(STDIN, STDOUT)->run(); + * @endCode + */ +trait LineSessionTrait { + + /** + * The onConnect() method is called when a new session is opened. + * + * @return string|null + * Header/welcome line, or NULL if none. + */ + protected function onConnect(): ?string { + return NULL; + } + + /** + * The onRequest() method is called after receiving one line of text. + * + * @param string $requestLine + * The received line of text. + * @return string|null + * The line to send back, or NULL if none. + */ + abstract protected function onRequest(string $requestLine): ?string; + + /** + * The onRequest() method is called after receiving one line of text. + * + * @param string $requestLine + * The received line of text - which led to the unhandled exception. + * @param \Throwable $t + * The unhandled exception. + * @return string|null + * The line to send back, or NULL if none. + */ + abstract protected function onException(string $requestLine, \Throwable $t): ?string; + + /** + * @var resource + * Ex: STDIN + */ + protected $input; + + /** + * @var resource + * Ex: STDOUT + */ + protected $output; + + /** + * Line-delimiter. + * + * @var string + */ + protected $delimiter = "\n"; + + /** + * Maximum size of the buffer for reading lines. + * + * Clients may need to set this if they submit large requests. + * + * @var int + */ + protected $maxLine = 524288; + + /** + * A value to display immediately before the response lines. + * + * Clients may set this is if they want to detect and skip buggy noise. + * + * @var string|null + * Ex: chr(1).chr(1) + */ + protected $responsePrefix = NULL; + + /** + * @param resource|null $input + * @param resource|null $output + */ + public function __construct($input = NULL, $output = NULL) { + $this->input = $input; + $this->output = $output; + } + + /** + * Run the main loop. Poll for commands on $input and write responses to $output. + */ + public function run() { + $this->write($this->onConnect()); + + while (FALSE !== ($line = stream_get_line($this->input, $this->maxLine, $this->delimiter))) { + $line = rtrim($line, $this->delimiter); + if (empty($line)) { + continue; + } + + try { + $response = $this->onRequest($line); + } + catch (\Throwable $t) { + $response = $this->onException($line, $t); + } + $this->write($response); + } + } + + /** + * @return int + */ + public function getMaxLine(): int { + return $this->maxLine; + } + + /** + * @param int $maxLine + * @return $this + */ + public function setMaxLine(int $maxLine) { + $this->maxLine = $maxLine; + return $this; + } + + /** + * @return string|null + */ + public function getResponsePrefix(): ?string { + return $this->responsePrefix; + } + + /** + * @param string|null $responsePrefix + * @return $this + */ + public function setResponsePrefix(?string $responsePrefix) { + $this->responsePrefix = $responsePrefix; + return $this; + } + + /** + * @param resource $input + * @param resource $output + * @return $this + */ + public function setIO($input, $output) { + $this->input = $input; + $this->output = $output; + return $this; + } + + protected function write(?string $response): void { + if ($response === NULL) { + return; + } + if ($this->responsePrefix !== NULL) { + fwrite($this->output, $this->responsePrefix); + } + fwrite($this->output, $response); + fwrite($this->output, $this->delimiter); + } + +} diff --git a/Civi/Pipe/PublicMethods.php b/Civi/Pipe/PublicMethods.php new file mode 100644 index 0000000000..c6cdd86f4a --- /dev/null +++ b/Civi/Pipe/PublicMethods.php @@ -0,0 +1,105 @@ + 3, 'check_permissions' => TRUE], $request[2] ?? []); + return civicrm_api(...$request); + } + + /** + * Send a request to APIv4. + * + * @param $session + * @param array $request + * Tuple: [$entity, $action, $params] + * @return array|\Civi\Api4\Generic\Result|int + */ + public function api4($session, $request) { + $request[2] = array_merge(['version' => 4, 'checkPermissions' => TRUE], $request[2] ?? []); + return civicrm_api(...$request); + } + + /** + * Simple test; send/receive a fragment of data. + * + * @param $session + * @param mixed $request + * @return mixed + */ + public function echo($session, $request) { + return $request; + } + + /** + * Set active user. + * + * @param $session + * @param array{contactId: int, userId: int, user: string} $request + * @return array|\Civi\Api4\Generic\Result|int + */ + public function login($session, $request) { + if (!function_exists('authx_login')) { + throw new \CRM_Core_Exception("Cannot authenticate. Authx is not configured."); + } + $auth = authx_login($request, FALSE /* Pipe sessions do not need cookies or DB */); + return \CRM_Utils_Array::subset($auth, ['contactId', 'userId']); + } + + /** + * Set ephemeral session options. + * + * @param $session + * @param array{maxLines: int, responsePrefix: int} $request + * Any updates to perform. May be empty/omitted. + * @return array{maxLines: int, responsePrefix: int} + * List of updated options. + * If the list of updates was empty, then return all options. + */ + public function options($session, $request) { + $map = [ + 'responsePrefix' => $session, + 'maxLine' => $session, + ]; + + $result = []; + if (!empty($request)) { + foreach ($request as $option => $value) { + if (isset($map[$option])) { + $storage = $map[$option]; + $storage->{'set' . ucfirst($option)}($value); + $result[$option] = $storage->{'get' . ucfirst($option)}(); + } + } + } + else { + foreach ($map as $option => $storage) { + $result[$option] = $storage->{'get' . ucfirst($option)}(); + } + } + return $result; + } + +} -- 2.25.1