CommunityMessages.php 6.86 KB
Newer Older
1 2 3
<?php
/*
 +--------------------------------------------------------------------+
totten's avatar
totten committed
4
 | CiviCRM version 5                                                  |
5
 +--------------------------------------------------------------------+
6
 | Copyright CiviCRM LLC (c) 2004-2019                                |
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 +--------------------------------------------------------------------+
 | This file is a part of CiviCRM.                                    |
 |                                                                    |
 | CiviCRM is free software; you can copy, modify, and distribute it  |
 | under the terms of the GNU Affero General Public License           |
 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
 |                                                                    |
 | CiviCRM is distributed in the hope that it will be useful, but     |
 | WITHOUT ANY WARRANTY; without even the implied warranty of         |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
 | See the GNU Affero General Public License for more details.        |
 |                                                                    |
 | You should have received a copy of the GNU Affero General Public   |
 | License and the CiviCRM Licensing Exception along                  |
 | with this program; if not, contact CiviCRM LLC                     |
 | at info[AT]civicrm[DOT]org. If you have questions about the        |
 | GNU Affero General Public License or the licensing of CiviCRM,     |
 | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
 +--------------------------------------------------------------------+
26
 */
27 28 29 30 31 32

/**
 * Manage the download, validation, and rendering of community messages
 */
class CRM_Core_CommunityMessages {

33
  const DEFAULT_MESSAGES_URL = 'https://alert.civicrm.org/alert?prot=1&ver={ver}&uf={uf}&sid={sid}&lang={lang}&co={co}';
34
  const DEFAULT_PERMISSION = 'administer CiviCRM';
35

36
  /**
37
   * Default time to wait before retrying.
38
   */
39 40
  // 2 hours
  const DEFAULT_RETRY = 7200;
41 42 43 44 45 46 47 48 49 50 51

  /**
   * @var CRM_Utils_HttpClient
   */
  protected $client;

  /**
   * @var CRM_Utils_Cache_Interface
   */
  protected $cache;

52 53 54 55 56
  /**
   * @var FALSE|string
   */
  protected $messagesUrl;

57
  /**
58
   * Create default instance.
59 60 61 62 63
   *
   * @return CRM_Core_CommunityMessages
   */
  public static function create() {
    return new CRM_Core_CommunityMessages(
64
      Civi::cache('community_messages'),
65 66 67 68
      CRM_Utils_HttpClient::singleton()
    );
  }

69 70 71
  /**
   * @param CRM_Utils_Cache_Interface $cache
   * @param CRM_Utils_HttpClient $client
72
   * @param null $messagesUrl
73
   */
74
  public function __construct($cache, $client, $messagesUrl = NULL) {
75 76
    $this->cache = $cache;
    $this->client = $client;
77
    if ($messagesUrl === NULL) {
78
      $this->messagesUrl = Civi::settings()->get('communityMessagesUrl');
79 80 81 82
    }
    else {
      $this->messagesUrl = $messagesUrl;
    }
83 84 85
    if ($this->messagesUrl === '*default*') {
      $this->messagesUrl = self::DEFAULT_MESSAGES_URL;
    }
86 87 88
  }

  /**
89
   * Get the messages document (either from the cache or by downloading)
90 91 92 93 94 95 96 97
   *
   * @return NULL|array
   */
  public function getDocument() {
    $isChanged = FALSE;
    $document = $this->cache->get('communityMessages');

    if (empty($document) || !is_array($document)) {
98 99
      $document = [
        'messages' => [],
100 101
        // ASAP
        'expires' => 0,
102 103
        'ttl' => self::DEFAULT_RETRY,
        'retry' => self::DEFAULT_RETRY,
104
      ];
105 106 107 108
      $isChanged = TRUE;
    }

    if ($document['expires'] <= CRM_Utils_Time::getTimeRaw()) {
109
      $newDocument = $this->fetchDocument();
110
      if ($newDocument && $this->validateDocument($newDocument)) {
111 112
        $document = $newDocument;
        $document['expires'] = CRM_Utils_Time::getTimeRaw() + $document['ttl'];
113 114
      }
      else {
115
        // keep the old messages for now, try again later
116 117 118 119 120 121 122 123 124 125 126 127 128
        $document['expires'] = CRM_Utils_Time::getTimeRaw() + $document['retry'];
      }
      $isChanged = TRUE;
    }

    if ($isChanged) {
      $this->cache->set('communityMessages', $document);
    }

    return $document;
  }

  /**
129
   * Download document from URL and parse as JSON.
130
   *
131 132
   * @return NULL|array
   *   parsed JSON
133
   */
134 135
  public function fetchDocument() {
    list($status, $json) = $this->client->get($this->getRenderedUrl());
136 137 138 139 140 141 142 143 144 145
    if ($status != CRM_Utils_HttpClient::STATUS_OK || empty($json)) {
      return NULL;
    }
    $doc = json_decode($json, TRUE);
    if (empty($doc) || json_last_error() != JSON_ERROR_NONE) {
      return NULL;
    }
    return $doc;
  }

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
  /**
   * Get the final, usable URL string (after interpolating any variables)
   *
   * @return FALSE|string
   */
  public function getRenderedUrl() {
    return CRM_Utils_System::evalUrl($this->messagesUrl);
  }

  /**
   * @return bool
   */
  public function isEnabled() {
    return $this->messagesUrl !== FALSE && $this->messagesUrl !== 'FALSE';
  }

162
  /**
163
   * Pick a message to display.
164 165 166
   *
   * @return NULL|array
   */
167 168
  public function pick() {
    $document = $this->getDocument();
169
    $messages = [];
170 171
    foreach ($document['messages'] as $message) {
      if (!isset($message['perms'])) {
172
        $message['perms'] = [self::DEFAULT_PERMISSION];
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
      }
      if (!CRM_Core_Permission::checkAnyPerm($message['perms'])) {
        continue;
      }

      if (isset($message['components'])) {
        $enabled = array_keys(CRM_Core_Component::getEnabledComponents());
        if (count(array_intersect($enabled, $message['components'])) == 0) {
          continue;
        }
      }

      $messages[] = $message;
    }
    if (empty($messages)) {
      return NULL;
    }

    $idx = rand(0, count($messages) - 1);
    return $messages[$idx];
193 194 195 196 197 198 199
  }

  /**
   * @param string $markup
   * @return string
   */
  public static function evalMarkup($markup) {
200
    $config = CRM_Core_Config::singleton();
201
    $vals = [
202 203 204 205
      'resourceUrl' => rtrim($config->resourceBase, '/'),
      'ver' => CRM_Utils_System::version(),
      'uf' => $config->userFramework,
      'php' => phpversion(),
206
      'sid' => CRM_Utils_System::getSiteID(),
207 208 209
      'baseUrl' => $config->userFrameworkBaseURL,
      'lang' => $config->lcMessages,
      'co' => $config->defaultContactCountry,
210 211
    ];
    $vars = [];
212
    foreach ($vals as $k => $v) {
213 214
      $vars['%%' . $k . '%%'] = $v;
      $vars['{{' . $k . '}}'] = urlencode($v);
215 216
    }
    return strtr($markup, $vars);
217 218
  }

219 220 221 222 223 224 225
  /**
   * Ensure that a document is well-formed
   *
   * @param array $document
   * @return bool
   */
  public function validateDocument($document) {
yashodha's avatar
yashodha committed
226
    if (!isset($document['ttl']) || !is_int($document['ttl'])) {
227 228
      return FALSE;
    }
yashodha's avatar
yashodha committed
229
    if (!isset($document['retry']) || !is_int($document['retry'])) {
230 231 232 233 234 235 236 237 238 239 240 241
      return FALSE;
    }
    if (!isset($document['messages']) || !is_array($document['messages'])) {
      return FALSE;
    }
    foreach ($document['messages'] as $message) {
      // TODO validate $message['markup']
    }

    return TRUE;
  }

242
}