<?php
/*
 +--------------------------------------------------------------------+
 | Copyright CiviCRM LLC. All rights reserved.                        |
 |                                                                    |
 | This work is published under the GNU AGPLv3 license with some      |
 | permitted exceptions and without any warranty. For full license    |
 | and copyright information, see https://civicrm.org/licensing       |
 +--------------------------------------------------------------------+
 */

/**
 * A queue implementation which stores items in the CiviCRM SQL database
 */
class CRM_Queue_Queue_Sql extends CRM_Queue_Queue {

  use CRM_Queue_Queue_SqlTrait;

  /**
   * Create a reference to queue. After constructing the queue, one should
   * usually call createQueue (if it's a new queue) or loadQueue (if it's
   * known to be an existing queue).
   *
   * @param array $queueSpec
   *   Array with keys:
   *   - type: string, required, e.g. "interactive", "immediate", "stomp",
   *     "beanstalk"
   *   - name: string, required, e.g. "upgrade-tasks"
   *   - reset: bool, optional; if a queue is found, then it should be
   *     flushed; default to TRUE
   *   - (additional keys depending on the queue provider).
   */
  public function __construct($queueSpec) {
    parent::__construct($queueSpec);
  }

  /**
   * Get the next item.
   *
   * @param int $lease_time
   *   Seconds.
   *
   * @return object
   *   With key 'data' that matches the inputted data.
   */
  public function claimItem($lease_time = 3600) {

    $result = NULL;
    $dao = CRM_Core_DAO::executeQuery('LOCK TABLES civicrm_queue_item WRITE;');
    $sql = "
        SELECT first_in_queue.* FROM (
          SELECT id, queue_name, submit_time, release_time, data
          FROM civicrm_queue_item
          WHERE queue_name = %1
          ORDER BY weight ASC, id ASC
          LIMIT 1
        ) first_in_queue
        WHERE release_time IS NULL OR release_time < %2
      ";
    $params = [
      1 => [$this->getName(), 'String'],
      2 => [CRM_Utils_Time::getTime(), 'Timestamp'],
    ];
    $dao = CRM_Core_DAO::executeQuery($sql, $params, TRUE, 'CRM_Queue_DAO_QueueItem');
    if (is_a($dao, 'DB_Error')) {
      // FIXME - Adding code to allow tests to pass
      throw new CRM_Core_Exception('Unable to claim queue item');
    }

    if ($dao->fetch()) {
      $nowEpoch = CRM_Utils_Time::getTimeRaw();
      CRM_Core_DAO::executeQuery("UPDATE civicrm_queue_item SET release_time = %1 WHERE id = %2", [
        '1' => [date('YmdHis', $nowEpoch + $lease_time), 'String'],
        '2' => [$dao->id, 'Integer'],
      ]);
      // (Comment by artfulrobot Sep 2019: Not sure what the below comment means, should be removed/clarified?)
      // work-around: inconsistent date-formatting causes unintentional breakage
      #        $dao->submit_time = date('YmdHis', strtotime($dao->submit_time));
      #        $dao->release_time = date('YmdHis', $nowEpoch + $lease_time);
      #        $dao->save();
      $dao->data = unserialize($dao->data);
      $result = $dao;
    }

    $dao = CRM_Core_DAO::executeQuery('UNLOCK TABLES;');

    return $result;
  }

  /**
   * Get the next item, even if there's an active lease
   *
   * @param int $lease_time
   *   Seconds.
   *
   * @return object
   *   With key 'data' that matches the inputted data.
   */
  public function stealItem($lease_time = 3600) {
    $sql = "
      SELECT id, queue_name, submit_time, release_time, data
      FROM civicrm_queue_item
      WHERE queue_name = %1
      ORDER BY weight ASC, id ASC
      LIMIT 1
    ";
    $params = [
      1 => [$this->getName(), 'String'],
    ];
    $dao = CRM_Core_DAO::executeQuery($sql, $params, TRUE, 'CRM_Queue_DAO_QueueItem');
    if ($dao->fetch()) {
      $nowEpoch = CRM_Utils_Time::getTimeRaw();
      CRM_Core_DAO::executeQuery("UPDATE civicrm_queue_item SET release_time = %1 WHERE id = %2", [
        '1' => [date('YmdHis', $nowEpoch + $lease_time), 'String'],
        '2' => [$dao->id, 'Integer'],
      ]);
      $dao->data = unserialize($dao->data);
      return $dao;
    }
  }

}
