PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/mailpoet/lib/Cron/CronHelper.php

https://gitlab.com/remyvianne/krowkaramel
PHP | 222 lines | 180 code | 29 blank | 13 comment | 17 complexity | 6e2cf59f7ffa65567e77289100c1b79a MD5 | raw file
  1. <?php
  2. namespace MailPoet\Cron;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Router\Endpoints\CronDaemon as CronDaemonEndpoint;
  5. use MailPoet\Router\Router;
  6. use MailPoet\Settings\SettingsController;
  7. use MailPoet\Util\Security;
  8. use MailPoet\WP\Functions as WPFunctions;
  9. class CronHelper {
  10. const DAEMON_EXECUTION_LIMIT = 20; // seconds
  11. const DAEMON_REQUEST_TIMEOUT = 5; // seconds
  12. const DAEMON_SETTING = 'cron_daemon';
  13. const DAEMON_STATUS_ACTIVE = 'active';
  14. const DAEMON_STATUS_INACTIVE = 'inactive';
  15. // Error codes
  16. const DAEMON_EXECUTION_LIMIT_REACHED = 1001;
  17. /** @var SettingsController */
  18. private $settings;
  19. /** @var WPFunctions */
  20. private $wp;
  21. public function __construct(
  22. SettingsController $settings,
  23. WPFunctions $wp
  24. ) {
  25. $this->settings = $settings;
  26. $this->wp = $wp;
  27. }
  28. public function getDaemonExecutionLimit() {
  29. $limit = $this->wp->applyFilters('mailpoet_cron_get_execution_limit', self::DAEMON_EXECUTION_LIMIT);
  30. return $limit;
  31. }
  32. public function getDaemonExecutionTimeout() {
  33. $limit = $this->getDaemonExecutionLimit();
  34. $timeout = $limit * 1.75;
  35. return $this->wp->applyFilters('mailpoet_cron_get_execution_timeout', $timeout);
  36. }
  37. public function createDaemon($token) {
  38. $daemon = [
  39. 'token' => $token,
  40. 'status' => self::DAEMON_STATUS_ACTIVE,
  41. 'run_accessed_at' => null,
  42. 'run_started_at' => null,
  43. 'run_completed_at' => null,
  44. 'last_error' => null,
  45. 'last_error_date' => null,
  46. ];
  47. $this->saveDaemon($daemon);
  48. return $daemon;
  49. }
  50. public function restartDaemon($token) {
  51. return $this->createDaemon($token);
  52. }
  53. public function getDaemon() {
  54. return $this->settings->fetch(self::DAEMON_SETTING);
  55. }
  56. public function saveDaemonLastError($error) {
  57. $daemon = $this->getDaemon();
  58. if ($daemon) {
  59. $daemon['last_error'] = $error;
  60. $daemon['last_error_date'] = time();
  61. $this->saveDaemon($daemon);
  62. }
  63. }
  64. public function saveDaemonRunCompleted($runCompletedAt) {
  65. $daemon = $this->getDaemon();
  66. if ($daemon) {
  67. $daemon['run_completed_at'] = $runCompletedAt;
  68. $this->saveDaemon($daemon);
  69. }
  70. }
  71. public function saveDaemon($daemon) {
  72. $daemon['updated_at'] = time();
  73. $this->settings->set(
  74. self::DAEMON_SETTING,
  75. $daemon
  76. );
  77. }
  78. public function deactivateDaemon($daemon) {
  79. $daemon['status'] = self::DAEMON_STATUS_INACTIVE;
  80. $this->settings->set(
  81. self::DAEMON_SETTING,
  82. $daemon
  83. );
  84. }
  85. public function createToken() {
  86. return Security::generateRandomString();
  87. }
  88. public function pingDaemon() {
  89. $url = $this->getCronUrl(
  90. CronDaemonEndpoint::ACTION_PING_RESPONSE
  91. );
  92. $result = $this->queryCronUrl($url);
  93. if (is_wp_error($result)) return $result->get_error_message();
  94. $response = $this->wp->wpRemoteRetrieveBody($result);
  95. $response = substr(trim($response), -strlen(DaemonHttpRunner::PING_SUCCESS_RESPONSE)) === DaemonHttpRunner::PING_SUCCESS_RESPONSE ?
  96. DaemonHttpRunner::PING_SUCCESS_RESPONSE :
  97. $response;
  98. return $response;
  99. }
  100. public function validatePingResponse($response) {
  101. return $response === DaemonHttpRunner::PING_SUCCESS_RESPONSE;
  102. }
  103. public function accessDaemon($token) {
  104. $data = ['token' => $token];
  105. $url = $this->getCronUrl(
  106. CronDaemonEndpoint::ACTION_RUN,
  107. $data
  108. );
  109. $daemon = $this->getDaemon();
  110. if (!$daemon) {
  111. throw new \LogicException('Daemon does not exist.');
  112. }
  113. $daemon['run_accessed_at'] = time();
  114. $this->saveDaemon($daemon);
  115. $result = $this->queryCronUrl($url);
  116. return $this->wp->wpRemoteRetrieveBody($result);
  117. }
  118. /**
  119. * @return bool|null
  120. */
  121. public function isDaemonAccessible() {
  122. $daemon = $this->getDaemon();
  123. if (!$daemon || !isset($daemon['run_accessed_at'])) {
  124. return null;
  125. }
  126. if ($daemon['run_accessed_at'] <= (int)$daemon['run_started_at']) {
  127. return true;
  128. }
  129. if (
  130. $daemon['run_accessed_at'] + self::DAEMON_REQUEST_TIMEOUT < time() &&
  131. $daemon['run_accessed_at'] > (int)$daemon['run_started_at']
  132. ) {
  133. return false;
  134. }
  135. return null;
  136. }
  137. public function queryCronUrl($url) {
  138. $args = $this->wp->applyFilters(
  139. 'mailpoet_cron_request_args',
  140. [
  141. 'blocking' => true,
  142. 'sslverify' => false,
  143. 'timeout' => self::DAEMON_REQUEST_TIMEOUT,
  144. 'user-agent' => 'MailPoet Cron',
  145. ]
  146. );
  147. return $this->wp->wpRemotePost($url, $args);
  148. }
  149. public function getCronUrl($action, $data = false) {
  150. $url = Router::buildRequest(
  151. CronDaemonEndpoint::ENDPOINT,
  152. $action,
  153. $data
  154. );
  155. $customCronUrl = $this->wp->applyFilters('mailpoet_cron_request_url', $url);
  156. return ($customCronUrl === $url) ?
  157. str_replace(home_url(), $this->getSiteUrl(), $url) :
  158. $customCronUrl;
  159. }
  160. public function getSiteUrl($siteUrl = false) {
  161. // additional check for some sites running inside a virtual machine or behind
  162. // proxy where there could be different ports (e.g., host:8080 => guest:80)
  163. $siteUrl = ($siteUrl) ? $siteUrl : WPFunctions::get()->homeUrl();
  164. $parsedUrl = parse_url($siteUrl);
  165. if (!is_array($parsedUrl)) {
  166. throw new \Exception(__('Site URL is unreachable.', 'mailpoet'));
  167. }
  168. $callScheme = '';
  169. if (isset($parsedUrl['scheme']) && ($parsedUrl['scheme'] === 'https')) {
  170. $callScheme = 'ssl://';
  171. }
  172. // 1. if site URL does not contain a port, return the URL
  173. if (!isset($parsedUrl['port']) || empty($parsedUrl['port'])) return $siteUrl;
  174. // 2. if site URL contains valid port, try connecting to it
  175. $urlHost = $parsedUrl['host'] ?? '';
  176. $fp = @fsockopen($callScheme . $urlHost, $parsedUrl['port'], $errno, $errstr, 1);
  177. if ($fp) return $siteUrl;
  178. // 3. if connection fails, attempt to connect the standard port derived from URL
  179. // schema
  180. $urlScheme = $parsedUrl['scheme'] ?? '';
  181. $port = (strtolower($urlScheme) === 'http') ? 80 : 443;
  182. $fp = @fsockopen($callScheme . $urlHost, $port, $errno, $errstr, 1);
  183. if ($fp) return sprintf('%s://%s', $urlScheme, $urlHost);
  184. // 4. throw an error if all connection attempts failed
  185. throw new \Exception(__('Site URL is unreachable.', 'mailpoet'));
  186. }
  187. public function enforceExecutionLimit($timer) {
  188. $elapsedTime = microtime(true) - $timer;
  189. if ($elapsedTime >= $this->getDaemonExecutionLimit()) {
  190. throw new \Exception(__('Maximum execution time has been reached.', 'mailpoet'), self::DAEMON_EXECUTION_LIMIT_REACHED);
  191. }
  192. }
  193. }