diff --git a/Civi/Api4/Action/ContactCategory/GetFlows.php b/Civi/Api4/Action/ContactCategory/GetFlows.php new file mode 100644 index 0000000..2a9d98f --- /dev/null +++ b/Civi/Api4/Action/ContactCategory/GetFlows.php @@ -0,0 +1,167 @@ +debug('Begin', ['=start' => 'GetFlows', '=timed' => 1, 'from' => $this->startDate, 'to' => $this->endDate]); + + // Check dates are valid and get a Ymd format of the day *following* the specified one. + // This is so we can find activities *before* this date and thereby capture activities that + // occurred *on* the given date. + $getValidDate = fn(?string $input) => $input ? (new DateTimeImmutable($input))->modify('+1 day')->format('Ymd'): FALSE; + + $startDate = $getValidDate($this->startDate); + if (!$startDate) { + throw new CRM_Core_Exception(E::ts("Cannot parse given start date.")); + } + $endDate = $getValidDate($this->endDate); + if (!$endDate) { + throw new CRM_Core_Exception(E::ts("Cannot parse given end date.")); + } + if (!($endDate > $startDate)) { + throw new CRM_Core_Exception(E::ts("This version of CiviCRM does not support twisting the space time continuum. End date cannot be before start date.")); + } + + $result['windowFunctionsSupported'] = $this->windowFunctionsSupported(); + if ($result['windowFunctionsSupported']) { + $this->solveWithWindowFunctions($result, $startDate, $endDate); + } + else { + // TODO: make a slow version that doesn't need window functions. + // $this->solveWithoutWindowFunctions($result); + throw new CRM_Core_Exception("your database does not support Window functions."); + } + + Civi::log()->debug("Complete.", ['=' => 'set', 'result' => $result->getArrayCopy()]); + } + + /** + * Does the database support window functions in the way we need? + * + * MariaDB 10.2+ does. + */ + protected function windowFunctionsSupported() { + $cache = \CRM_Utils_Cache::create(['type' => ['SqlGroup'], 'name' => 'contactcats']); + $supported = $cache->get('windowFunctionsSupported'); // Will return default if cached value expired. + $supported = null; + if ($supported === NULL) { + // Need to test for window functions. + $supported = FALSE; + // Prevent Civi handling possible execption + $handler = set_exception_handler(fn() => false); + try { + \CRM_Core_DAO::executeQuery("CREATE TEMPORARY TABLE contactcats_window_check (i int unsigned not null, x int unsigned not null)"); + \CRM_Core_DAO::executeQuery("INSERT INTO contactcats_window_check VALUES (1, 1), (2, 1), (3, 2);"); + $rows = \CRM_Core_DAO::executeQuery("SELECT x, ROW_NUMBER() OVER ( PARTITION BY (x) ORDER BY i DESC) rn FROM contactcats_window_check ORDER BY x, rn")->fetchAll(); + $supported = ($rows === [ [ 'x' => "1", 'rn' => "1"], [ 'x' => "1", 'rn' => "2"], [ 'x' => "2", 'rn' => "1"] ]) ; + } + catch (\Exception $e) { + // Silent fail. + } + // Restore exception handler + set_exception_handler($handler); + $cache->set('windowFunctionsSupported', $supported); + } + return $supported; + } + + /** + * + */ + protected function solveWithWindowFunctions(Result $result, string $startDateYmd, string $endDateYmd) { + + $sql = << [$endDateYmd, 'Int'], + 2 => [$startDateYmd, 'Int'], + ])->fetchAll(); + + // Don't use exchange array so as not to gazump non-array data(?) + foreach ($data as $row) { + $result[] = $row; + } + } + + + +} +