CrmUi - Fix crmSelect2 to work with ngOptions
[civicrm-core.git] / CRM / Core / CommunityMessages.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Manage the download, validation, and rendering of community messages
14 */
15 class CRM_Core_CommunityMessages {
16
17 const DEFAULT_MESSAGES_URL = 'https://alert.civicrm.org/alert?prot=1&ver={ver}&uf={uf}&sid={sid}&lang={lang}&co={co}';
18 const DEFAULT_PERMISSION = 'administer CiviCRM';
19
20 /**
21 * Default time to wait before retrying.
22 */
23 // 2 hours
24 const DEFAULT_RETRY = 7200;
25
26 /**
27 * @var CRM_Utils_HttpClient
28 */
29 protected $client;
30
31 /**
32 * @var CRM_Utils_Cache_Interface
33 */
34 protected $cache;
35
36 /**
37 * Url to retrieve community messages from.
38 *
39 * False means a retrieval will not be attempted.
40 *
41 * @var false|string
42 */
43 protected $messagesUrl;
44
45 /**
46 * Create default instance.
47 *
48 * @return CRM_Core_CommunityMessages
49 */
50 public static function create() {
51 return new CRM_Core_CommunityMessages(
52 Civi::cache('community_messages'),
53 CRM_Utils_HttpClient::singleton()
54 );
55 }
56
57 /**
58 * Class constructor.
59 *
60 * @param CRM_Utils_Cache_Interface $cache
61 * @param CRM_Utils_HttpClient $client
62 * @param string|false $messagesUrl
63 */
64 public function __construct($cache, $client, $messagesUrl = NULL) {
65 $this->cache = $cache;
66 $this->client = $client;
67 if ($messagesUrl === NULL) {
68 $this->messagesUrl = Civi::settings()->get('communityMessagesUrl');
69 }
70 else {
71 $this->messagesUrl = $messagesUrl;
72 }
73 if ($this->messagesUrl === '*default*') {
74 $this->messagesUrl = self::DEFAULT_MESSAGES_URL;
75 }
76 }
77
78 /**
79 * Get the messages document (either from the cache or by downloading).
80 *
81 * @return NULL|array
82 */
83 public function getDocument() {
84 $isChanged = FALSE;
85 $document = $this->cache->get('communityMessages');
86
87 if (empty($document) || !is_array($document)) {
88 $document = [
89 'messages' => [],
90 // ASAP
91 'expires' => 0,
92 'ttl' => self::DEFAULT_RETRY,
93 'retry' => self::DEFAULT_RETRY,
94 ];
95 $isChanged = TRUE;
96 }
97
98 $refTime = CRM_Utils_Time::getTimeRaw();
99 if ($document['expires'] <= $refTime) {
100 $newDocument = $this->fetchDocument();
101 if ($newDocument && $this->validateDocument($newDocument)) {
102 $document = $newDocument;
103 $document['expires'] = $refTime + $document['ttl'];
104 }
105 else {
106 // keep the old messages for now, try again later
107 $document['expires'] = $refTime + $document['retry'];
108 }
109 $isChanged = TRUE;
110 }
111
112 if ($isChanged) {
113 $this->cache->set('communityMessages', $document);
114 }
115
116 return $document;
117 }
118
119 /**
120 * Download document from URL and parse as JSON.
121 *
122 * @return NULL|array
123 * parsed JSON
124 */
125 public function fetchDocument() {
126 list($status, $json) = $this->client->get($this->getRenderedUrl());
127 if ($status != CRM_Utils_HttpClient::STATUS_OK || empty($json)) {
128 return NULL;
129 }
130 $doc = json_decode($json, TRUE);
131 if (empty($doc) || json_last_error() != JSON_ERROR_NONE) {
132 return NULL;
133 }
134 return $doc;
135 }
136
137 /**
138 * Get the final, usable URL string (after interpolating any variables)
139 *
140 * @return FALSE|string
141 */
142 public function getRenderedUrl() {
143 return CRM_Utils_System::evalUrl($this->messagesUrl);
144 }
145
146 /**
147 * @return bool
148 */
149 public function isEnabled() {
150 return $this->messagesUrl !== FALSE && $this->messagesUrl !== 'FALSE';
151 }
152
153 /**
154 * Pick a message to display.
155 *
156 * @return NULL|array
157 */
158 public function pick() {
159 $document = $this->getDocument();
160 $messages = [];
161 foreach ($document['messages'] as $message) {
162 if (!isset($message['perms'])) {
163 $message['perms'] = [self::DEFAULT_PERMISSION];
164 }
165 if (!CRM_Core_Permission::checkAnyPerm($message['perms'])) {
166 continue;
167 }
168
169 if (isset($message['components'])) {
170 $enabled = array_keys(CRM_Core_Component::getEnabledComponents());
171 if (count(array_intersect($enabled, $message['components'])) == 0) {
172 continue;
173 }
174 }
175
176 $messages[] = $message;
177 }
178 if (empty($messages)) {
179 return NULL;
180 }
181
182 $idx = rand(0, count($messages) - 1);
183 return $messages[$idx];
184 }
185
186 /**
187 * @param string $markup
188 * @return string
189 */
190 public static function evalMarkup($markup) {
191 $config = CRM_Core_Config::singleton();
192 $vals = [
193 'resourceUrl' => rtrim($config->resourceBase, '/'),
194 'ver' => CRM_Utils_System::version(),
195 'uf' => $config->userFramework,
196 'php' => phpversion(),
197 'sid' => CRM_Utils_System::getSiteID(),
198 'baseUrl' => $config->userFrameworkBaseURL,
199 'lang' => $config->lcMessages,
200 'co' => $config->defaultContactCountry ?? '',
201 ];
202 $vars = [];
203 foreach ($vals as $k => $v) {
204 $vars['%%' . $k . '%%'] = $v;
205 $vars['{{' . $k . '}}'] = urlencode($v);
206 }
207 return strtr($markup, $vars);
208 }
209
210 /**
211 * Ensure that a document is well-formed
212 *
213 * @param array $document
214 * @return bool
215 */
216 public function validateDocument($document) {
217 if (!isset($document['ttl']) || !is_int($document['ttl'])) {
218 return FALSE;
219 }
220 if (!isset($document['retry']) || !is_int($document['retry'])) {
221 return FALSE;
222 }
223 if (!isset($document['messages']) || !is_array($document['messages'])) {
224 return FALSE;
225 }
226 foreach ($document['messages'] as $message) {
227 // TODO validate $message['markup']
228 }
229
230 return TRUE;
231 }
232
233 }