1 |
85ad3d82
|
Assos Assos
|
<?php
|
2 |
|
|
|
3 |
|
|
/**
|
4 |
|
|
* @file
|
5 |
|
|
* Allows users to send private messages to other users.
|
6 |
|
|
*/
|
7 |
|
|
|
8 |
|
|
/**
|
9 |
|
|
* Status constant for read messages.
|
10 |
|
|
*/
|
11 |
|
|
define('PRIVATEMSG_READ', 0);
|
12 |
|
|
/**
|
13 |
|
|
* Status constant for unread messages.
|
14 |
|
|
*/
|
15 |
|
|
define('PRIVATEMSG_UNREAD', 1);
|
16 |
|
|
/**
|
17 |
|
|
* Show unlimited messages in a thread.
|
18 |
|
|
*/
|
19 |
|
|
define('PRIVATEMSG_UNLIMITED', 'unlimited');
|
20 |
|
|
|
21 |
|
|
/**
|
22 |
|
|
* Implements hook_permission().
|
23 |
|
|
*/
|
24 |
|
|
function privatemsg_permission() {
|
25 |
|
|
return array(
|
26 |
|
|
'administer privatemsg settings' => array(
|
27 |
|
|
'title' => t('Administer privatemsg'),
|
28 |
|
|
'description' => t('Perform maintenance tasks for privatemsg'),
|
29 |
|
|
),
|
30 |
|
|
'read privatemsg' => array(
|
31 |
|
|
'title' => t('Read private messages'),
|
32 |
|
|
'description' => t('Read private messages'),
|
33 |
|
|
),
|
34 |
|
|
'read all private messages' => array(
|
35 |
|
|
'title' => t('Read all private messages'),
|
36 |
|
|
'description' => t('Includes messages of other users'),
|
37 |
|
|
),
|
38 |
|
|
'write privatemsg' => array(
|
39 |
|
|
'title' => t('Write new private messages'),
|
40 |
|
|
'description' => t('Write new private messages'),
|
41 |
|
|
),
|
42 |
|
|
'delete privatemsg' => array(
|
43 |
|
|
'title' => t('Delete private messages'),
|
44 |
|
|
'description' => t('Delete private messages'),
|
45 |
|
|
),
|
46 |
|
|
'allow disabling privatemsg' => array(
|
47 |
|
|
'title' => t('Allow disabling private messages'),
|
48 |
|
|
'description' => t("Allows user to disable privatemsg so that they can't receive or send any private messages.")
|
49 |
|
|
),
|
50 |
|
|
'reply only privatemsg' => array(
|
51 |
|
|
'title' => t('Reply to private messages'),
|
52 |
|
|
'description' => t('Allows to reply to private messages but not send new ones. Note that the write new private messages permission includes replies.')
|
53 |
|
|
),
|
54 |
|
|
'use tokens in privatemsg' => array(
|
55 |
|
|
'title' => t('Use tokens in private messages'),
|
56 |
|
|
'description' => t("Allows user to use available tokens when sending private messages.")
|
57 |
|
|
),
|
58 |
|
|
'select text format for privatemsg' => array(
|
59 |
|
|
'title' => t('Select text format for private messages'),
|
60 |
|
|
'description' => t('Allows to choose the text format when sending private messages. Otherwise, the default is used.'),
|
61 |
|
|
),
|
62 |
|
|
);
|
63 |
|
|
}
|
64 |
|
|
|
65 |
|
|
/**
|
66 |
|
|
* Generate array of user objects based on a string.
|
67 |
|
|
*
|
68 |
|
|
*
|
69 |
|
|
* @param $userstring
|
70 |
|
|
* A string with user id, for example 1,2,4. Returned by the list query.
|
71 |
|
|
*
|
72 |
|
|
* @return
|
73 |
|
|
* Array with user objects.
|
74 |
|
|
*/
|
75 |
|
|
function _privatemsg_generate_user_array($string, $slice = NULL) {
|
76 |
|
|
// Convert user uid list (uid1,uid2,uid3) into an array. If $slice is not NULL
|
77 |
|
|
// pass that as argument to array_slice(). For example, -4 will only load the
|
78 |
|
|
// last four users.
|
79 |
|
|
// This is done to avoid loading user objects that are not displayed, for
|
80 |
|
|
// obvious performance reasons.
|
81 |
|
|
$users = explode(',', $string);
|
82 |
|
|
if (!is_null($slice)) {
|
83 |
|
|
$users = array_slice($users, $slice);
|
84 |
|
|
}
|
85 |
|
|
$participants = array();
|
86 |
|
|
foreach ($users as $uid) {
|
87 |
|
|
// If it is an integer, it is a user id.
|
88 |
|
|
if ((int)$uid > 0) {
|
89 |
|
|
$user_ids = privatemsg_user_load_multiple(array($uid));
|
90 |
|
|
if ($account = array_shift($user_ids)) {
|
91 |
|
|
$participants[privatemsg_recipient_key($account)] = $account;
|
92 |
|
|
}
|
93 |
|
|
}
|
94 |
|
|
elseif (strpos($uid, '_') !== FALSE) {
|
95 |
|
|
list($type, $id) = explode('_', $uid);
|
96 |
|
|
$type_info = privatemsg_recipient_get_type($type);
|
97 |
|
|
if ($type_info && isset($type_info['load']) && is_callable($type_info['load'])) {
|
98 |
|
|
$temp_load = $type_info['load'](array($id));
|
99 |
|
|
if ($participant = array_shift($temp_load)) {
|
100 |
|
|
$participants[privatemsg_recipient_key($participant)] = $participant;
|
101 |
|
|
}
|
102 |
|
|
}
|
103 |
|
|
}
|
104 |
|
|
}
|
105 |
|
|
return $participants;
|
106 |
|
|
}
|
107 |
|
|
|
108 |
|
|
/**
|
109 |
|
|
* Format an array of user objects.
|
110 |
|
|
*
|
111 |
|
|
* @param $part_array
|
112 |
|
|
* Array with user objects, for example the one returned by
|
113 |
|
|
* _privatemsg_generate_user_array.
|
114 |
|
|
*
|
115 |
|
|
* @param $limit
|
116 |
|
|
* Limit the number of user objects which should be displayed.
|
117 |
|
|
* @param $no_text
|
118 |
|
|
* When TRUE, don't display the Participants/From text.
|
119 |
|
|
* @return
|
120 |
|
|
* String with formatted user objects, like user1, user2.
|
121 |
|
|
*/
|
122 |
|
|
function _privatemsg_format_participants($part_array, $limit = NULL, $no_text = FALSE) {
|
123 |
|
|
global $user;
|
124 |
|
|
if (count($part_array) > 0) {
|
125 |
|
|
$to = array();
|
126 |
|
|
$limited = FALSE;
|
127 |
|
|
foreach ($part_array as $account) {
|
128 |
|
|
|
129 |
|
|
// Directly address the current user.
|
130 |
|
|
if (isset($account->type) && in_array($account->type, array('hidden', 'user')) && $account->recipient == $user->uid) {
|
131 |
|
|
array_unshift($to, $no_text ? t('You') : t('you'));
|
132 |
|
|
continue;
|
133 |
|
|
}
|
134 |
|
|
|
135 |
|
|
// Don't display recipients with type hidden.
|
136 |
|
|
if (isset($account->type) && $account->type == 'hidden') {
|
137 |
|
|
continue;
|
138 |
|
|
}
|
139 |
|
|
if (is_int($limit) && count($to) >= $limit) {
|
140 |
|
|
$limited = TRUE;
|
141 |
|
|
break;
|
142 |
|
|
}
|
143 |
|
|
$to[] = privatemsg_recipient_format($account);
|
144 |
|
|
}
|
145 |
|
|
|
146 |
|
|
$limit_string = '';
|
147 |
|
|
if ($limited) {
|
148 |
|
|
$limit_string = t(' and others');
|
149 |
|
|
}
|
150 |
|
|
|
151 |
|
|
|
152 |
|
|
if ($no_text) {
|
153 |
|
|
return implode(', ', $to) . $limit_string;
|
154 |
|
|
}
|
155 |
|
|
|
156 |
|
|
$last = array_pop($to);
|
157 |
|
|
if (count($to) == 0) { // Only one participant
|
158 |
|
|
return t("From !last", array('!last' => $last));
|
159 |
|
|
}
|
160 |
|
|
else { // Multiple participants..
|
161 |
|
|
$participants = implode(', ', $to);
|
162 |
|
|
return t('Between !participants and !last', array('!participants' => $participants, '!last' => $last));
|
163 |
|
|
}
|
164 |
|
|
}
|
165 |
|
|
return '';
|
166 |
|
|
}
|
167 |
|
|
|
168 |
|
|
/**
|
169 |
|
|
* Implements hook_menu().
|
170 |
|
|
*/
|
171 |
|
|
function privatemsg_menu() {
|
172 |
|
|
$items['messages'] = array(
|
173 |
|
|
'title' => 'Messages',
|
174 |
|
|
'title callback' => 'privatemsg_title_callback',
|
175 |
|
|
'page callback' => 'privatemsg_list_page',
|
176 |
|
|
'page arguments' => array('list'),
|
177 |
|
|
'file' => 'privatemsg.pages.inc',
|
178 |
|
|
'access callback' => 'privatemsg_user_access',
|
179 |
|
|
'type' => MENU_NORMAL_ITEM,
|
180 |
|
|
'menu_name' => 'user-menu',
|
181 |
|
|
);
|
182 |
|
|
$items['messages/list'] = array(
|
183 |
|
|
'title' => 'Messages',
|
184 |
|
|
'page callback' => 'privatemsg_list_page',
|
185 |
|
|
'page arguments' => array('list'),
|
186 |
|
|
'file' => 'privatemsg.pages.inc',
|
187 |
|
|
'access callback' => 'privatemsg_user_access',
|
188 |
|
|
'type' => MENU_DEFAULT_LOCAL_TASK,
|
189 |
|
|
'weight' => -10,
|
190 |
|
|
'menu_name' => 'user-menu',
|
191 |
|
|
);
|
192 |
|
|
$items['messages/view/%privatemsg_thread'] = array(
|
193 |
|
|
// Set the third argument to TRUE so that we can show access denied instead
|
194 |
|
|
// of not found.
|
195 |
|
|
'load arguments' => array(NULL, NULL, TRUE),
|
196 |
|
|
'title' => 'Read message',
|
197 |
|
|
'page callback' => 'privatemsg_view',
|
198 |
|
|
'page arguments' => array(2),
|
199 |
|
|
'file' => 'privatemsg.pages.inc',
|
200 |
|
|
'access callback' => 'privatemsg_view_access',
|
201 |
|
|
'access arguments' => array(2),
|
202 |
|
|
'type' => MENU_LOCAL_TASK,
|
203 |
|
|
'weight' => -5,
|
204 |
|
|
'menu_name' => 'user-menu',
|
205 |
|
|
);
|
206 |
|
|
$items['messages/delete/%privatemsg_thread/%privatemsg_message'] = array(
|
207 |
|
|
'title' => 'Delete message',
|
208 |
|
|
'page callback' => 'drupal_get_form',
|
209 |
|
|
'page arguments' => array('privatemsg_delete', 2, 3),
|
210 |
|
|
'file' => 'privatemsg.pages.inc',
|
211 |
|
|
'access callback' => 'privatemsg_user_access',
|
212 |
|
|
'access arguments' => array('delete privatemsg'),
|
213 |
|
|
'type' => MENU_CALLBACK,
|
214 |
|
|
'weight' => -10,
|
215 |
|
|
'menu_name' => 'user-menu',
|
216 |
|
|
);
|
217 |
|
|
$items['messages/new'] = array(
|
218 |
|
|
'title' => 'Write new message',
|
219 |
|
|
'page callback' => 'drupal_get_form',
|
220 |
|
|
'page arguments' => array('privatemsg_new', 2, 3, NULL),
|
221 |
|
|
'file' => 'privatemsg.pages.inc',
|
222 |
|
|
'access callback' => 'privatemsg_user_access',
|
223 |
|
|
'access arguments' => array('write privatemsg'),
|
224 |
|
|
'type' => MENU_LOCAL_ACTION,
|
225 |
|
|
'weight' => -3,
|
226 |
|
|
'menu_name' => 'user-menu',
|
227 |
|
|
);
|
228 |
|
|
// Auto-completes available user names & removes duplicates.
|
229 |
|
|
$items['messages/autocomplete'] = array(
|
230 |
|
|
'page callback' => 'privatemsg_autocomplete',
|
231 |
|
|
'file' => 'privatemsg.pages.inc',
|
232 |
|
|
'access callback' => 'privatemsg_user_access',
|
233 |
|
|
'access arguments' => array('write privatemsg'),
|
234 |
|
|
'type' => MENU_CALLBACK,
|
235 |
|
|
);
|
236 |
|
|
$items['admin/config/messaging'] = array(
|
237 |
|
|
'title' => 'Messaging',
|
238 |
|
|
'description' => 'Messaging systems.',
|
239 |
|
|
'page callback' => 'system_admin_menu_block_page',
|
240 |
|
|
'access arguments' => array('access administration pages'),
|
241 |
|
|
'file' => 'system.admin.inc',
|
242 |
|
|
'file path' => drupal_get_path('module', 'system'),
|
243 |
|
|
);
|
244 |
|
|
$items['admin/config/messaging/privatemsg'] = array(
|
245 |
|
|
'title' => 'Private message settings',
|
246 |
|
|
'description' => 'Configure private messaging settings.',
|
247 |
|
|
'page callback' => 'drupal_get_form',
|
248 |
|
|
'page arguments' => array('privatemsg_admin_settings'),
|
249 |
|
|
'file' => 'privatemsg.admin.inc',
|
250 |
|
|
'access arguments' => array('administer privatemsg settings'),
|
251 |
|
|
'type' => MENU_NORMAL_ITEM,
|
252 |
|
|
);
|
253 |
|
|
$items['admin/config/messaging/privatemsg/settings'] = array(
|
254 |
|
|
'title' => 'Private message settings',
|
255 |
|
|
'description' => 'Configure private messaging settings.',
|
256 |
|
|
'page callback' => 'drupal_get_form',
|
257 |
|
|
'page arguments' => array('privatemsg_admin_settings'),
|
258 |
|
|
'file' => 'privatemsg.admin.inc',
|
259 |
|
|
'access arguments' => array('administer privatemsg settings'),
|
260 |
|
|
'type' => MENU_DEFAULT_LOCAL_TASK,
|
261 |
|
|
'weight' => -10,
|
262 |
|
|
);
|
263 |
|
|
$items['messages/undo/action'] = array(
|
264 |
|
|
'title' => 'Private messages',
|
265 |
|
|
'description' => 'Undo last thread action',
|
266 |
|
|
'page callback' => 'privatemsg_undo_action',
|
267 |
|
|
'file' => 'privatemsg.pages.inc',
|
268 |
|
|
'access arguments' => array('read privatemsg'),
|
269 |
|
|
'type' => MENU_CALLBACK,
|
270 |
|
|
'menu' => 'user-menu',
|
271 |
|
|
);
|
272 |
|
|
$items['user/%/messages'] = array(
|
273 |
|
|
'title' => 'Messages',
|
274 |
|
|
'page callback' => 'privatemsg_list_page',
|
275 |
|
|
'page arguments' => array('list', 1),
|
276 |
|
|
'file' => 'privatemsg.pages.inc',
|
277 |
|
|
'access callback' => 'privatemsg_user_access',
|
278 |
|
|
'access arguments' => array('read all private messages'),
|
279 |
|
|
'type' => MENU_LOCAL_TASK,
|
280 |
|
|
);
|
281 |
|
|
|
282 |
|
|
return $items;
|
283 |
|
|
}
|
284 |
|
|
|
285 |
|
|
/**
|
286 |
|
|
* Implements hook_menu_local_tasks_alter().
|
287 |
|
|
*/
|
288 |
|
|
function privatemsg_menu_local_tasks_alter(&$data, $router_item, $root_path) {
|
289 |
|
|
// Add action link to 'messages/new' on 'messages' page.
|
290 |
|
|
$add_to_array = array('messages/list', 'messages/inbox', 'messages/sent');
|
291 |
|
|
foreach ($add_to_array as $add_to) {
|
292 |
|
|
if (strpos($root_path, $add_to) !== FALSE) {
|
293 |
|
|
$item = menu_get_item('messages/new');
|
294 |
|
|
if ($item['access']) {
|
295 |
|
|
$data['actions']['output'][] = array(
|
296 |
|
|
'#theme' => 'menu_local_action',
|
297 |
|
|
'#link' => $item,
|
298 |
|
|
);
|
299 |
|
|
}
|
300 |
|
|
break;
|
301 |
|
|
}
|
302 |
|
|
}
|
303 |
|
|
}
|
304 |
|
|
|
305 |
|
|
/**
|
306 |
|
|
* Privatemsg wrapper for user_access.
|
307 |
|
|
*
|
308 |
|
|
* Never allows anonymous user access as that doesn't makes sense.
|
309 |
|
|
*
|
310 |
|
|
* @param $permission
|
311 |
|
|
* Permission string, defaults to read privatemsg
|
312 |
|
|
*
|
313 |
|
|
* @return
|
314 |
|
|
* TRUE if user has access, FALSE if not
|
315 |
|
|
*
|
316 |
|
|
* @ingroup api
|
317 |
|
|
*/
|
318 |
|
|
function privatemsg_user_access($permission = 'read privatemsg', $account = NULL) {
|
319 |
|
|
static $disabled_displayed = FALSE;
|
320 |
|
|
if ( $account === NULL ) {
|
321 |
|
|
global $user;
|
322 |
|
|
$account = $user;
|
323 |
|
|
}
|
324 |
|
|
if (!$account->uid) { // Disallow anonymous access, regardless of permissions
|
325 |
|
|
return FALSE;
|
326 |
|
|
}
|
327 |
|
|
if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
|
328 |
|
|
if (arg(0) == 'messages' && variable_get('privatemsg_display_disabled_message', TRUE) && !$disabled_displayed) {
|
329 |
|
|
$disabled_displayed = TRUE;
|
330 |
|
|
drupal_set_message(t('You have disabled Privatemsg and are not allowed to write messages. Go to your <a href="@settings_url">Account settings</a> to enable it again.', array('@settings_url' => url('user/' . $account->uid . '/edit'))), 'warning');
|
331 |
|
|
}
|
332 |
|
|
return FALSE;
|
333 |
|
|
}
|
334 |
|
|
if (!user_access($permission, $account)) {
|
335 |
|
|
return FALSE;
|
336 |
|
|
}
|
337 |
|
|
return TRUE;
|
338 |
|
|
}
|
339 |
|
|
|
340 |
|
|
/**
|
341 |
|
|
* Check access to the view messages page.
|
342 |
|
|
*
|
343 |
|
|
* Function to restrict the access of the view messages page to just the
|
344 |
|
|
* messages/view/% pages and not to leave tabs artifact on other lower
|
345 |
|
|
* level pages such as the messages/new/%.
|
346 |
|
|
*
|
347 |
|
|
* @param $thread
|
348 |
|
|
* A array containing all information about a specific thread, generated by
|
349 |
|
|
* privatemsg_thread_load().
|
350 |
|
|
*
|
351 |
|
|
* @ingroup api
|
352 |
|
|
*/
|
353 |
|
|
function privatemsg_view_access($thread) {
|
354 |
|
|
// Do not allow access to threads without messages.
|
355 |
|
|
if (empty($thread['messages'])) {
|
356 |
|
|
// Count all messages, if there
|
357 |
|
|
return FALSE;
|
358 |
|
|
}
|
359 |
|
|
if (privatemsg_user_access('read privatemsg') && arg(1) == 'view') {
|
360 |
|
|
return TRUE;
|
361 |
|
|
}
|
362 |
|
|
return FALSE;
|
363 |
|
|
}
|
364 |
|
|
|
365 |
|
|
/**
|
366 |
|
|
* Checks the status of private messaging for provided user.
|
367 |
|
|
*
|
368 |
|
|
* @param user object to check
|
369 |
|
|
* @return TRUE if user has disabled private messaging, FALSE otherwise
|
370 |
|
|
*/
|
371 |
|
|
function privatemsg_is_disabled($account) {
|
372 |
|
|
if (!$account || !isset($account->uid) || !$account->uid) {
|
373 |
|
|
return FALSE;
|
374 |
|
|
}
|
375 |
|
|
|
376 |
|
|
if (!isset($account->privatemsg_disabled)) {
|
377 |
|
|
// Make sure we have a fully loaded user object and try to load it if not.
|
378 |
|
|
if ((!empty($account->roles) || $account = user_load($account->uid)) && user_access('allow disabling privatemsg', $account)) {
|
379 |
|
|
$account->privatemsg_disabled = (bool)db_query('SELECT 1 FROM {pm_disable} WHERE uid = :uid ', array(':uid' => $account->uid))->fetchField();
|
380 |
|
|
}
|
381 |
|
|
else {
|
382 |
|
|
$account->privatemsg_disabled = FALSE;
|
383 |
|
|
}
|
384 |
|
|
}
|
385 |
|
|
|
386 |
|
|
return $account->privatemsg_disabled;
|
387 |
|
|
}
|
388 |
|
|
|
389 |
|
|
/**
|
390 |
|
|
* Load a thread with all the messages and participants.
|
391 |
|
|
*
|
392 |
|
|
* This function is called by the menu system through the %privatemsg_thread
|
393 |
|
|
* wildcard.
|
394 |
|
|
*
|
395 |
|
|
* @param $thread_id
|
396 |
|
|
* Thread id, pmi.thread_id or pm.mid of the first message in that thread.
|
397 |
|
|
* @param $account
|
398 |
|
|
* User object for which the thread should be loaded, defaults to
|
399 |
|
|
* the current user.
|
400 |
|
|
* @param $start
|
401 |
|
|
* Message offset from the start of the thread.
|
402 |
|
|
* @param $useAccessDenied
|
403 |
|
|
* Set to TRUE if the function should forward to the access denied page
|
404 |
|
|
* instead of not found. This is used by the menu system because that does
|
405 |
|
|
* load arguments before access checks are made. Defaults to FALSE.
|
406 |
|
|
*
|
407 |
|
|
* @return
|
408 |
|
|
* $thread object, with keys messages, participants, title and user. messages
|
409 |
|
|
* contains an array of messages, participants an array of user, subject the
|
410 |
|
|
* subject of the thread and user the user viewing the thread.
|
411 |
|
|
*
|
412 |
|
|
* If no messages are found, or the thread_id is invalid, the function returns
|
413 |
|
|
* FALSE.
|
414 |
|
|
|
415 |
|
|
* @ingroup api
|
416 |
|
|
*/
|
417 |
|
|
function privatemsg_thread_load($thread_id, $account = NULL, $start = NULL, $useAccessDenied = FALSE) {
|
418 |
|
|
$threads = &drupal_static(__FUNCTION__, array());
|
419 |
|
|
$thread_id = (int)$thread_id;
|
420 |
|
|
if ($thread_id > 0) {
|
421 |
|
|
$thread = array('thread_id' => $thread_id);
|
422 |
|
|
|
423 |
|
|
if (is_null($account)) {
|
424 |
|
|
global $user;
|
425 |
|
|
$account = clone $user;
|
426 |
|
|
}
|
427 |
|
|
|
428 |
|
|
if (!isset($threads[$account->uid])) {
|
429 |
|
|
$threads[$account->uid] = array();
|
430 |
|
|
}
|
431 |
|
|
|
432 |
|
|
if (!array_key_exists($thread_id, $threads[$account->uid])) {
|
433 |
|
|
// Load the list of participants.
|
434 |
|
|
$thread['participants'] = _privatemsg_load_thread_participants($thread_id, $account, FALSE, 'view');
|
435 |
|
|
$thread['read_all'] = FALSE;
|
436 |
|
|
if (empty($thread['participants']) && privatemsg_user_access('read all private messages', $account)) {
|
437 |
|
|
$thread['read_all'] = TRUE;
|
438 |
|
|
// Load all participants.
|
439 |
|
|
$thread['participants'] = _privatemsg_load_thread_participants($thread_id, FALSE, FALSE, 'view');
|
440 |
|
|
}
|
441 |
|
|
|
442 |
|
|
// Load messages returned by the messages query with privatemsg_message_load_multiple().
|
443 |
|
|
$query = _privatemsg_assemble_query('messages', array($thread_id), $thread['read_all'] ? NULL : $account);
|
444 |
|
|
// Use subquery to bypass group by since it is not possible to alter
|
445 |
|
|
// existing GROUP BY statements.
|
446 |
|
|
$countQuery = db_select($query);
|
447 |
|
|
$countQuery->addExpression('COUNT(*)');
|
448 |
|
|
$thread['message_count'] = $thread['to'] = $countQuery->execute()->fetchField();
|
449 |
|
|
$thread['from'] = 1;
|
450 |
|
|
// Check if we need to limit the messages.
|
451 |
|
|
$max_amount = variable_get('privatemsg_view_max_amount', 20);
|
452 |
|
|
|
453 |
|
|
// If there is no start value, select based on get params.
|
454 |
|
|
if (is_null($start)) {
|
455 |
|
|
if (isset($_GET['start']) && $_GET['start'] < $thread['message_count']) {
|
456 |
|
|
$start = $_GET['start'];
|
457 |
|
|
}
|
458 |
|
|
elseif (!variable_get('privatemsg_view_use_max_as_default', FALSE) && $max_amount == PRIVATEMSG_UNLIMITED) {
|
459 |
|
|
$start = PRIVATEMSG_UNLIMITED;
|
460 |
|
|
}
|
461 |
|
|
else {
|
462 |
|
|
$start = $thread['message_count'] - (variable_get('privatemsg_view_use_max_as_default', FALSE) ? variable_get('privatemsg_view_default_amount', 10) : $max_amount);
|
463 |
|
|
}
|
464 |
|
|
}
|
465 |
|
|
|
466 |
|
|
if ($start != PRIVATEMSG_UNLIMITED) {
|
467 |
|
|
if ($max_amount == PRIVATEMSG_UNLIMITED) {
|
468 |
|
|
$last_page = 0;
|
469 |
|
|
$max_amount = $thread['message_count'];
|
470 |
|
|
}
|
471 |
|
|
else {
|
472 |
|
|
// Calculate the number of messages on the "last" page to avoid
|
473 |
|
|
// message overlap.
|
474 |
|
|
// Note - the last page lists the earliest messages, not the latest.
|
475 |
|
|
$paging_count = variable_get('privatemsg_view_use_max_as_default', FALSE) ? $thread['message_count'] - variable_get('privatemsg_view_default_amount', 10) : $thread['message_count'];
|
476 |
|
|
$last_page = $paging_count % $max_amount;
|
477 |
|
|
}
|
478 |
|
|
|
479 |
|
|
// Sanity check - we cannot start from a negative number.
|
480 |
|
|
if ($start < 0) {
|
481 |
|
|
$start = 0;
|
482 |
|
|
}
|
483 |
|
|
$thread['start'] = $start;
|
484 |
|
|
|
485 |
|
|
//If there are newer messages on the page, show pager link allowing to go to the newer messages.
|
486 |
|
|
if (($start + $max_amount + 1) < $thread['message_count']) {
|
487 |
|
|
$thread['to'] = $start + $max_amount;
|
488 |
|
|
$thread['newer_start'] = $start + $max_amount;
|
489 |
|
|
}
|
490 |
|
|
if ($start - $max_amount >= 0) {
|
491 |
|
|
$thread['older_start'] = $start - $max_amount;
|
492 |
|
|
}
|
493 |
|
|
elseif ($start > 0) {
|
494 |
|
|
$thread['older_start'] = 0;
|
495 |
|
|
}
|
496 |
|
|
|
497 |
|
|
// Do not show messages on the last page that would show on the page
|
498 |
|
|
// before. This will only work when using the visual pager.
|
499 |
|
|
if ($start < $last_page && $max_amount != PRIVATEMSG_UNLIMITED && $max_amount < $thread['message_count']) {
|
500 |
|
|
unset($thread['older_start']);
|
501 |
|
|
$thread['to'] = $thread['newer_start'] = $max_amount = $last_page;
|
502 |
|
|
// Start from the first message - this is a specific hack to make sure
|
503 |
|
|
// the message display has sane paging on the last page.
|
504 |
|
|
$start = 0;
|
505 |
|
|
}
|
506 |
|
|
// Visual counts start from 1 instead of zero, so plus one.
|
507 |
|
|
$thread['from'] = $start + 1;
|
508 |
|
|
$query->range($start, $max_amount);
|
509 |
|
|
}
|
510 |
|
|
$conditions = array();
|
511 |
|
|
if (!$thread['read_all']) {
|
512 |
|
|
$conditions['account'] = $account;
|
513 |
|
|
}
|
514 |
|
|
$thread['messages'] = privatemsg_message_load_multiple($query->execute()->fetchCol(), $conditions);
|
515 |
|
|
|
516 |
|
|
// If there are no messages, don't allow access to the thread.
|
517 |
|
|
if (empty($thread['messages'])) {
|
518 |
|
|
if ($useAccessDenied) {
|
519 |
|
|
// Generate new query with read all to see if the thread does exist.
|
520 |
|
|
$query = _privatemsg_assemble_query('messages', array($thread_id), NULL);
|
521 |
|
|
$exists = $query->countQuery()->execute()->fetchField();
|
522 |
|
|
if (!$exists) {
|
523 |
|
|
// Thread does not exist, display 404.
|
524 |
|
|
$thread = FALSE;
|
525 |
|
|
}
|
526 |
|
|
}
|
527 |
|
|
else {
|
528 |
|
|
$thread = FALSE;
|
529 |
|
|
}
|
530 |
|
|
}
|
531 |
|
|
else {
|
532 |
|
|
// General data, assume subject is the same for all messages of that thread.
|
533 |
|
|
$thread['user'] = $account;
|
534 |
|
|
$message = current($thread['messages']);
|
535 |
|
|
$thread['subject'] = $thread['subject-original'] = $message->subject;
|
536 |
|
|
if ($message->has_tokens) {
|
537 |
|
|
$thread['subject'] = privatemsg_token_replace($thread['subject'], array('privatemsg_message' => $message), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
|
538 |
|
|
}
|
539 |
|
|
}
|
540 |
|
|
$threads[$account->uid][$thread_id] = $thread;
|
541 |
|
|
}
|
542 |
|
|
return $threads[$account->uid][$thread_id];
|
543 |
|
|
}
|
544 |
|
|
return FALSE;
|
545 |
|
|
}
|
546 |
|
|
|
547 |
|
|
/**
|
548 |
|
|
* Implements hook_privatemsg_view_template().
|
549 |
|
|
*
|
550 |
|
|
* Allows modules to define different message view template.
|
551 |
|
|
*
|
552 |
|
|
* This hook returns information about available themes for privatemsg viewing.
|
553 |
|
|
*
|
554 |
|
|
* array(
|
555 |
|
|
* 'machine_template_name' => 'Human readable template name',
|
556 |
|
|
* 'machine_template_name_2' => 'Human readable template name 2'
|
557 |
|
|
* };
|
558 |
|
|
*/
|
559 |
|
|
function privatemsg_privatemsg_view_template() {
|
560 |
|
|
return array(
|
561 |
|
|
'privatemsg-view' => 'Default view',
|
562 |
|
|
);
|
563 |
|
|
}
|
564 |
|
|
|
565 |
|
|
/**
|
566 |
|
|
* Implements hook_cron().
|
567 |
|
|
*
|
568 |
|
|
* If the flush feature is enabled, a given amount of deleted messages that are
|
569 |
|
|
* old enough are flushed.
|
570 |
|
|
*/
|
571 |
|
|
function privatemsg_cron() {
|
572 |
|
|
if (variable_get('privatemsg_flush_enabled', FALSE)) {
|
573 |
|
|
$query = _privatemsg_assemble_query('deleted', variable_get('privatemsg_flush_days', 30), variable_get('privatemsg_flush_max', 200));
|
574 |
|
|
|
575 |
|
|
foreach ($query->execute()->fetchCol() as $mid) {
|
576 |
|
|
$message = privatemsg_message_load($mid);
|
577 |
|
|
module_invoke_all('privatemsg_message_flush', $message);
|
578 |
|
|
|
579 |
|
|
// Delete recipients of the message.
|
580 |
|
|
db_delete('pm_index')
|
581 |
|
|
->condition('mid', $mid)
|
582 |
|
|
->execute();
|
583 |
|
|
// Delete message itself.
|
584 |
|
|
db_delete('pm_message')
|
585 |
|
|
->condition('mid', $mid)
|
586 |
|
|
->execute();
|
587 |
|
|
}
|
588 |
|
|
}
|
589 |
|
|
|
590 |
|
|
// Number of user ids to process for this cron run.
|
591 |
|
|
$total_remaining = variable_get('privatemgs_cron_recipient_per_run', 1000);
|
592 |
|
|
$current_process = variable_get('privatemsg_cron_recipient_process', array());
|
593 |
|
|
|
594 |
|
|
// Instead of doing the order by in the database, which can be slow, we load
|
595 |
|
|
// all results and the do the handling there. Additionally, explicitly specify
|
596 |
|
|
// the desired types. If there are more than a few dozen results the site is
|
597 |
|
|
// unhealthy anyway because this cron is unable to keep up with the
|
598 |
|
|
// unprocessed recipients.
|
599 |
|
|
$rows = array();
|
600 |
|
|
|
601 |
|
|
// Get all type keys except user.
|
602 |
|
|
$types = privatemsg_recipient_get_types();
|
603 |
|
|
unset($types['user']);
|
604 |
|
|
$types = array_keys($types);
|
605 |
|
|
|
606 |
|
|
// If there are no other recipient types, there is nothing to do.
|
607 |
|
|
if (empty($types)) {
|
608 |
|
|
return;
|
609 |
|
|
}
|
610 |
|
|
|
611 |
|
|
$result = db_query("SELECT pmi.recipient, pmi.type, pmi.mid FROM {pm_index} pmi WHERE pmi.type IN (:types) AND pmi.is_new = 1", array(':types' => $types));
|
612 |
|
|
foreach ($result as $row) {
|
613 |
|
|
// If this is equal to the row that is currently processed, add it first in
|
614 |
|
|
// the array.
|
615 |
|
|
if (!empty($current_process) && $current_process['mid'] == $row->mid && $current_process['type'] == $row->type && $current_process['recipient'] == $row->recipient) {
|
616 |
|
|
array_unshift($rows, $row);
|
617 |
|
|
}
|
618 |
|
|
else {
|
619 |
|
|
$rows[] = $row;
|
620 |
|
|
}
|
621 |
|
|
}
|
622 |
|
|
|
623 |
|
|
foreach ($rows as $row) {
|
624 |
|
|
$type = privatemsg_recipient_get_type($row->type);
|
625 |
|
|
if (isset($type['load']) && is_callable($type['load'])) {
|
626 |
|
|
$loaded = $type['load'](array($row->recipient));
|
627 |
|
|
if (empty($loaded)) {
|
628 |
|
|
continue;
|
629 |
|
|
}
|
630 |
|
|
$recipient = reset($loaded);
|
631 |
|
|
}
|
632 |
|
|
|
633 |
|
|
// Check if we already started to process this recipient.
|
634 |
|
|
$offset = 0;
|
635 |
|
|
if (!empty($current_process) && $current_process['mid'] == $row->mid && $current_process['recipient'] == $row->recipient && $current_process['type'] == $row->type) {
|
636 |
|
|
$offset = $current_process['offset'];
|
637 |
|
|
}
|
638 |
|
|
|
639 |
|
|
$load_function = $type['generate recipients'];
|
640 |
|
|
$uids = $load_function($recipient, $total_remaining, $offset);
|
641 |
|
|
if (!empty($uids)) {
|
642 |
|
|
foreach ($uids as $uid) {
|
643 |
|
|
privatemsg_message_change_recipient($row->mid, $uid, 'hidden');
|
644 |
|
|
}
|
645 |
|
|
}
|
646 |
|
|
// If less than the total remaining uids were returned, we are finished.
|
647 |
|
|
if (count($uids) < $total_remaining) {
|
648 |
|
|
$total_remaining -= count($uids);
|
649 |
|
|
db_update('pm_index')
|
650 |
|
|
->fields(array('is_new' => PRIVATEMSG_READ))
|
651 |
|
|
->condition('mid', $row->mid)
|
652 |
|
|
->condition('recipient', $row->recipient)
|
653 |
|
|
->condition('type', $row->type)
|
654 |
|
|
->execute();
|
655 |
|
|
// Reset current process if necessary.
|
656 |
|
|
if ($offset > 0) {
|
657 |
|
|
variable_set('privatemsg_cron_recipient_process', array());
|
658 |
|
|
}
|
659 |
|
|
}
|
660 |
|
|
else {
|
661 |
|
|
// We are not yet finished, save current process and break.
|
662 |
|
|
$existing_offset = isset($current_process['offset']) ? $current_process['offset'] : 0;
|
663 |
|
|
$current_process = (array)$row;
|
664 |
|
|
$current_process['offset'] = $existing_offset + count($uids);
|
665 |
|
|
variable_set('privatemsg_cron_recipient_process', $current_process);
|
666 |
|
|
break;
|
667 |
|
|
}
|
668 |
|
|
}
|
669 |
|
|
}
|
670 |
|
|
|
671 |
|
|
function privatemsg_theme() {
|
672 |
|
|
$templates = array(
|
673 |
|
|
'privatemsg_view' => array(
|
674 |
|
|
'variables' => array('message' => NULL),
|
675 |
|
|
'template' => variable_get('private_message_view_template', 'privatemsg-view'), // 'privatemsg',
|
676 |
|
|
),
|
677 |
|
|
'privatemsg_from' => array(
|
678 |
|
|
'variables' => array('author' => NULL),
|
679 |
|
|
'template' => 'privatemsg-from',
|
680 |
|
|
),
|
681 |
|
|
'privatemsg_recipients' => array(
|
682 |
|
|
'variables' => array('message' => NULL),
|
683 |
|
|
'template' => 'privatemsg-recipients',
|
684 |
|
|
),
|
685 |
|
|
'privatemsg_between' => array(
|
686 |
|
|
'variables' => array('recipients' => NULL),
|
687 |
|
|
'template' => 'privatemsg-between',
|
688 |
|
|
),
|
689 |
|
|
// Define pattern for header/field templates. The theme system will register all
|
690 |
|
|
// theme functions that start with the defined pattern.
|
691 |
|
|
'privatemsg_list_header' => array(
|
692 |
|
|
'file' => 'privatemsg.theme.inc',
|
693 |
|
|
'path' => drupal_get_path('module', 'privatemsg'),
|
694 |
|
|
'pattern' => 'privatemsg_list_header__',
|
695 |
|
|
'variables' => array(),
|
696 |
|
|
),
|
697 |
|
|
'privatemsg_list_field' => array(
|
698 |
|
|
'file' => 'privatemsg.theme.inc',
|
699 |
|
|
'path' => drupal_get_path('module', 'privatemsg'),
|
700 |
|
|
'pattern' => 'privatemsg_list_field__',
|
701 |
|
|
'variables' => array('thread' => array()),
|
702 |
|
|
),
|
703 |
|
|
'privatemsg_new_block' => array(
|
704 |
|
|
'file' => 'privatemsg.theme.inc',
|
705 |
|
|
'path' => drupal_get_path('module', 'privatemsg'),
|
706 |
|
|
'variables' => array('count'),
|
707 |
|
|
),
|
708 |
|
|
'privatemsg_username' => array(
|
709 |
|
|
'file' => 'privatemsg.theme.inc',
|
710 |
|
|
'path' => drupal_get_path('module', 'privatemsg'),
|
711 |
|
|
'variables' => array('recipient' => NULL, 'options' => array()),
|
712 |
|
|
),
|
713 |
|
|
);
|
714 |
|
|
// Include the theme file to load the theme suggestions.
|
715 |
|
|
module_load_include('inc', 'privatemsg', 'privatemsg.theme');
|
716 |
|
|
$templates += drupal_find_theme_functions($templates, array('theme'));
|
717 |
|
|
return $templates;
|
718 |
|
|
}
|
719 |
|
|
|
720 |
|
|
function template_preprocess_privatemsg_view(&$vars) {
|
721 |
|
|
global $user;
|
722 |
|
|
|
723 |
|
|
$message = $vars['message'];
|
724 |
|
|
$vars['mid'] = isset($message->mid) ? $message->mid : NULL;
|
725 |
|
|
$vars['message_classes'] = isset($message->classes) ? $message->classes : array();
|
726 |
|
|
$vars['thread_id'] = isset($message->thread_id) ? $message->thread_id : NULL;
|
727 |
|
|
$vars['author_picture'] = theme('user_picture', array('account' => $message->author));
|
728 |
|
|
// Directly address the current user if he is the author.
|
729 |
|
|
if ($user->uid == $message->author->uid) {
|
730 |
|
|
$vars['author_name_link'] = t('You');
|
731 |
|
|
}
|
732 |
|
|
else {
|
733 |
|
|
$vars['author_name_link'] = privatemsg_recipient_format($message->author);
|
734 |
|
|
}
|
735 |
|
|
$vars['message_timestamp'] = privatemsg_format_date($message->timestamp);
|
736 |
|
|
|
737 |
|
|
$message->content = array(
|
738 |
|
|
'#view_mode' => 'message',
|
739 |
|
|
'body' => array(
|
740 |
|
|
'#markup' => check_markup($message->body, $message->format),
|
741 |
|
|
'#weight' => -4,
|
742 |
|
|
),
|
743 |
|
|
);
|
744 |
|
|
|
745 |
|
|
if ($message->has_tokens) {
|
746 |
|
|
// Replace tokens including option to add a notice if the user is not a
|
747 |
|
|
// recipient.
|
748 |
|
|
$message->content['body']['#markup'] = privatemsg_token_replace($message->content['body']['#markup'], array('privatemsg_message' => $message), array('privatemsg-token-notice' => TRUE, 'sanitize' => TRUE));
|
749 |
|
|
}
|
750 |
|
|
|
751 |
|
|
// Build fields content.
|
752 |
|
|
field_attach_prepare_view('privatemsg_message', array($vars['mid'] => $message), 'message');
|
753 |
|
|
$message->content += field_attach_view('privatemsg_message', $message, 'message');
|
754 |
|
|
|
755 |
|
|
// Render message body.
|
756 |
|
|
$vars['message_body'] = drupal_render($message->content);
|
757 |
|
|
if (isset($vars['mid']) && isset($vars['thread_id']) && privatemsg_user_access('delete privatemsg')) {
|
758 |
|
|
$vars['message_actions'][] = array('title' => t('Delete'), 'href' => 'messages/delete/' . $vars['thread_id'] . '/' . $vars['mid']);
|
759 |
|
|
}
|
760 |
|
|
$vars['message_anchors'][] = 'privatemsg-mid-' . $vars['mid'];
|
761 |
|
|
if (!empty($message->is_new)) {
|
762 |
|
|
$vars['message_anchors'][] = 'new';
|
763 |
|
|
$vars['new'] = drupal_ucfirst(t('new'));
|
764 |
|
|
}
|
765 |
|
|
|
766 |
|
|
// call hook_privatemsg_message_view_alter
|
767 |
|
|
drupal_alter('privatemsg_message_view', $vars);
|
768 |
|
|
|
769 |
|
|
$vars['message_actions'] = !empty($vars['message_actions']) ? theme('links', array('links' => $vars['message_actions'], 'attributes' => array('class' => array('privatemsg-message-actions', 'links', 'inline')))) : '';
|
770 |
|
|
|
771 |
|
|
$vars['anchors'] = '';
|
772 |
|
|
foreach ($vars['message_anchors'] as $anchor) {
|
773 |
|
|
$vars['anchors'] .= '<a name="' . $anchor . '"></a>';
|
774 |
|
|
}
|
775 |
|
|
}
|
776 |
|
|
|
777 |
|
|
function template_preprocess_privatemsg_recipients(&$vars) {
|
778 |
|
|
$vars['participants'] = ''; // assign a default empty value
|
779 |
|
|
if (isset($vars['thread']['participants'])) {
|
780 |
|
|
$vars['participants'] = _privatemsg_format_participants($vars['thread']['participants']);
|
781 |
|
|
}
|
782 |
|
|
}
|
783 |
|
|
|
784 |
|
|
/**
|
785 |
|
|
* Changes the read/new status of a single message.
|
786 |
|
|
*
|
787 |
|
|
* @param $pmid
|
788 |
|
|
* Message id
|
789 |
|
|
* @param $status
|
790 |
|
|
* Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD
|
791 |
|
|
* @param $account
|
792 |
|
|
* User object, defaults to the current user
|
793 |
|
|
*/
|
794 |
|
|
function privatemsg_message_change_status($pmid, $status, $account = NULL) {
|
795 |
|
|
if (!$account) {
|
796 |
|
|
global $user;
|
797 |
|
|
$account = $user;
|
798 |
|
|
}
|
799 |
|
|
db_update('pm_index')
|
800 |
|
|
->fields(array('is_new' => $status))
|
801 |
|
|
->condition('mid', $pmid)
|
802 |
|
|
->condition('recipient', $account->uid)
|
803 |
|
|
->condition('type', array('hidden', 'user'))
|
804 |
|
|
->execute();
|
805 |
|
|
|
806 |
|
|
// Allows modules to respond to the status change.
|
807 |
|
|
module_invoke_all('privatemsg_message_status_changed', $pmid, $status, $account);
|
808 |
|
|
}
|
809 |
|
|
|
810 |
|
|
/**
|
811 |
|
|
* Return number of unread messages for an account.
|
812 |
|
|
*
|
813 |
|
|
* @param $account
|
814 |
|
|
* Specify the user for which the unread count should be loaded.
|
815 |
|
|
*
|
816 |
|
|
* @ingroup api
|
817 |
|
|
*/
|
818 |
|
|
function privatemsg_unread_count($account = NULL) {
|
819 |
|
|
$counts = &drupal_static(__FUNCTION__, array());
|
820 |
|
|
if (!$account || $account->uid == 0) {
|
821 |
|
|
global $user;
|
822 |
|
|
$account = $user;
|
823 |
|
|
}
|
824 |
|
|
if (!isset($counts[$account->uid])) {
|
825 |
|
|
$counts[$account->uid] = _privatemsg_assemble_query('unread_count', $account)
|
826 |
|
|
->execute()
|
827 |
|
|
->fetchField();
|
828 |
|
|
}
|
829 |
|
|
return $counts[$account->uid];
|
830 |
|
|
}
|
831 |
|
|
|
832 |
|
|
/**
|
833 |
|
|
* Load all participants of a thread.
|
834 |
|
|
*
|
835 |
|
|
* @param $thread_id
|
836 |
|
|
* Thread ID for which the participants should be loaded.
|
837 |
|
|
* @param $account
|
838 |
|
|
* For which account should the messages be loaded. *
|
839 |
|
|
* @param $ignore_hidden
|
840 |
|
|
* Ignores hidden participants.
|
841 |
|
|
* @param $access
|
842 |
|
|
* Which access permission should be checked (write or view).
|
843 |
|
|
*
|
844 |
|
|
* @return
|
845 |
|
|
* Array with all visible/writable participants for that thread.
|
846 |
|
|
*/
|
847 |
|
|
function _privatemsg_load_thread_participants($thread_id, $account, $ignore_hidden = TRUE, $access = 'write') {
|
848 |
|
|
$query = _privatemsg_assemble_query('participants', $thread_id, $account);
|
849 |
|
|
$participants = array();
|
850 |
|
|
$to_load = array();
|
851 |
|
|
foreach ($query->execute() as $participant) {
|
852 |
|
|
if ($ignore_hidden && $participant->type == 'hidden') {
|
853 |
|
|
continue;
|
854 |
|
|
}
|
855 |
|
|
elseif (privatemsg_recipient_access($participant->type, $access, $participant)) {
|
856 |
|
|
$to_load[$participant->type][] = $participant->recipient;
|
857 |
|
|
}
|
858 |
|
|
}
|
859 |
|
|
|
860 |
|
|
// Now, load all non-user recipients.
|
861 |
|
|
foreach ($to_load as $type => $ids) {
|
862 |
|
|
$type_info = privatemsg_recipient_get_type($type);
|
863 |
|
|
if (isset($type_info['load']) && is_callable($type_info['load'])) {
|
864 |
|
|
$loaded = $type_info['load']($ids);
|
865 |
|
|
if (is_array($loaded)) {
|
866 |
|
|
$participants += $loaded;
|
867 |
|
|
}
|
868 |
|
|
}
|
869 |
|
|
}
|
870 |
|
|
if ($access == 'write' && $account) {
|
871 |
|
|
// Remove author if loading participants for writing and when he is not the
|
872 |
|
|
// only recipient.
|
873 |
|
|
if (isset($participants['user_' . $account->uid]) && count($participants) > 1) {
|
874 |
|
|
unset($participants['user_' . $account->uid]);
|
875 |
|
|
}
|
876 |
|
|
}
|
877 |
|
|
return $participants;
|
878 |
|
|
}
|
879 |
|
|
|
880 |
|
|
/**
|
881 |
|
|
* Extract the valid usernames of a string and loads them.
|
882 |
|
|
*
|
883 |
|
|
* This function is used to parse a string supplied by a username autocomplete
|
884 |
|
|
* field and load all user objects.
|
885 |
|
|
*
|
886 |
|
|
* @param $string
|
887 |
|
|
* A string in the form "usernameA, usernameB, ...".
|
888 |
|
|
* @return $type
|
889 |
|
|
* Array of recipient types this should be limited to.
|
890 |
|
|
*
|
891 |
|
|
* @return
|
892 |
|
|
* Array, first element is an array of loaded user objects, second an array
|
893 |
|
|
* with invalid names.
|
894 |
|
|
*
|
895 |
|
|
*/
|
896 |
|
|
function _privatemsg_parse_userstring($input, $types_limitations = array()) {
|
897 |
|
|
if (is_string($input)) {
|
898 |
|
|
$input = explode(',', $input);
|
899 |
|
|
}
|
900 |
|
|
|
901 |
|
|
// Start working through the input array.
|
902 |
|
|
$invalid = array();
|
903 |
|
|
$recipients = array();
|
904 |
|
|
$duplicates = array();
|
905 |
|
|
$denieds = array();
|
906 |
|
|
foreach ($input as $string) {
|
907 |
|
|
$string = trim($string);
|
908 |
|
|
// Ignore spaces.
|
909 |
|
|
if (!empty($string)) {
|
910 |
|
|
|
911 |
|
|
// First, collect all matches.
|
912 |
|
|
$matches = array();
|
913 |
|
|
|
914 |
|
|
// Remember if a possible match denies access.
|
915 |
|
|
$access_denied = FALSE;
|
916 |
|
|
|
917 |
|
|
// Collect matches from hook implementations.
|
918 |
|
|
foreach (module_implements('privatemsg_name_lookup') as $module) {
|
919 |
|
|
$function = $module . '_privatemsg_name_lookup';
|
920 |
|
|
$return = $function($string);
|
921 |
|
|
if (isset($return) && is_array($return)) {
|
922 |
|
|
foreach ($return as $recipient) {
|
923 |
|
|
// Save recipients under their key to merge recipients which were
|
924 |
|
|
// loaded multiple times.
|
925 |
|
|
if (empty($recipient->type)) {
|
926 |
|
|
$recipient->type = 'user';
|
927 |
|
|
$recipient->recipient = $recipient->uid;
|
928 |
|
|
}
|
929 |
|
|
$matches[privatemsg_recipient_key($recipient)] = $recipient;
|
930 |
|
|
}
|
931 |
|
|
}
|
932 |
|
|
}
|
933 |
|
|
|
934 |
|
|
foreach ($matches as $key => $recipient) {
|
935 |
|
|
// Check permissions, remove any recipients the user doesn't have write
|
936 |
|
|
// access for.
|
937 |
|
|
if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
|
938 |
|
|
unset($matches[$key]);
|
939 |
|
|
$access_denied = TRUE;
|
940 |
|
|
}
|
941 |
|
|
// Apply limitations.
|
942 |
|
|
if (!empty($types_limitations) && !in_array($recipient->type, $types_limitations)) {
|
943 |
|
|
unset($matches[$key]);
|
944 |
|
|
}
|
945 |
|
|
}
|
946 |
|
|
|
947 |
|
|
// Allow modules to alter the found matches.
|
948 |
|
|
drupal_alter('privatemsg_name_lookup_matches', $matches, $string);
|
949 |
|
|
|
950 |
|
|
// Check if there are any matches.
|
951 |
|
|
$number_of_matches = count($matches);
|
952 |
|
|
switch ($number_of_matches) {
|
953 |
|
|
case 1:
|
954 |
|
|
// Only a single match found, add to recipients.
|
955 |
|
|
$recipients += $matches;
|
956 |
|
|
break;
|
957 |
|
|
case 0:
|
958 |
|
|
// No match found, check if access was denied.
|
959 |
|
|
if ($access_denied) {
|
960 |
|
|
// There were possible matches, but access was denied.
|
961 |
|
|
$denieds[$string] = $string;
|
962 |
|
|
}
|
963 |
|
|
else {
|
964 |
|
|
// The string does not contain any valid recipients.
|
965 |
|
|
$invalid[$string] = $string;
|
966 |
|
|
}
|
967 |
|
|
break;
|
968 |
|
|
|
969 |
|
|
default:
|
970 |
|
|
// Multiple matches were found. The user has to specify which one he
|
971 |
|
|
// meant.
|
972 |
|
|
$duplicates[$string] = $matches;
|
973 |
|
|
break;
|
974 |
|
|
}
|
975 |
|
|
}
|
976 |
|
|
}
|
977 |
|
|
// Todo: Provide better API.
|
978 |
|
|
return array($recipients, $invalid, $duplicates, $denieds);
|
979 |
|
|
}
|
980 |
|
|
|
981 |
|
|
/**
|
982 |
|
|
* Implements hook_privatemsg_name_lookup().
|
983 |
|
|
*/
|
984 |
|
|
function privatemsg_privatemsg_name_lookup($string) {
|
985 |
|
|
// Remove optional user specifier.
|
986 |
|
|
$string = trim(str_replace('[user]', '', $string));
|
987 |
|
|
// Fall back to the default username lookup.
|
988 |
|
|
if (!$error = module_invoke('user', 'validate_name', $string)) {
|
989 |
|
|
// String is a valid username, look it up.
|
990 |
|
|
if ($recipient = user_load_by_name($string)) {
|
991 |
|
|
$recipient->recipient = $recipient->uid;
|
992 |
|
|
$recipient->type = 'user';
|
993 |
|
|
return array(privatemsg_recipient_key($recipient) => $recipient);
|
994 |
|
|
}
|
995 |
|
|
}
|
996 |
|
|
}
|
997 |
|
|
|
998 |
|
|
/**
|
999 |
|
|
* @addtogroup sql
|
1000 |
|
|
* @{
|
1001 |
|
|
*/
|
1002 |
|
|
|
1003 |
|
|
/**
|
1004 |
|
|
* Query definition to load a list of threads.
|
1005 |
|
|
*
|
1006 |
|
|
* @param $account
|
1007 |
|
|
* User object for which the messages are being loaded.
|
1008 |
|
|
* @param $argument
|
1009 |
|
|
* string argument which can be used in the query builder to modify the thread listing.
|
1010 |
|
|
*
|
1011 |
|
|
* @see hook_query_privatemsg_list_alter()
|
1012 |
|
|
*/
|
1013 |
|
|
function privatemsg_sql_list($account, $argument = 'list') {
|
1014 |
|
|
$query = db_select('pm_message', 'pm')->extend('TableSort')->extend('PagerDefault');
|
1015 |
|
|
$query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');
|
1016 |
|
|
|
1017 |
|
|
// Create count query;
|
1018 |
|
|
$count_query = db_select('pm_message', 'pm');
|
1019 |
|
|
$count_query->addExpression('COUNT(DISTINCT pmi.thread_id)', 'count');
|
1020 |
|
|
$count_query->join('pm_index', 'pmi', 'pm.mid = pmi.mid');
|
1021 |
|
|
$count_query
|
1022 |
|
|
->condition('pmi.recipient', $account->uid)
|
1023 |
|
|
->condition('pmi.type', array('hidden', 'user'))
|
1024 |
|
|
->condition('pmi.deleted', 0);
|
1025 |
|
|
$query->setCountQuery($count_query);
|
1026 |
|
|
|
1027 |
|
|
|
1028 |
|
|
// Required columns
|
1029 |
|
|
$query->addField('pmi', 'thread_id');
|
1030 |
|
|
$query->addExpression('MIN(pm.subject)', 'subject');
|
1031 |
|
|
$query->addExpression('MAX(pm.timestamp)', 'last_updated');
|
1032 |
|
|
$query->addExpression('MAX(pm.has_tokens)', 'has_tokens');
|
1033 |
|
|
$query->addExpression('SUM(pmi.is_new)', 'is_new');
|
1034 |
|
|
|
1035 |
|
|
// Load enabled columns
|
1036 |
|
|
$fields = array_filter(variable_get('privatemsg_display_fields', array('participants')));
|
1037 |
|
|
|
1038 |
|
|
if (in_array('count', $fields)) {
|
1039 |
|
|
// We only want the distinct number of messages in this thread.
|
1040 |
|
|
$query->addExpression('COUNT(distinct pmi.mid)', 'count');
|
1041 |
|
|
}
|
1042 |
|
|
if (in_array('participants', $fields)) {
|
1043 |
|
|
// Query for a string with uids, for example "1,6,7". This needs a subquery on PostgreSQL.
|
1044 |
|
|
if (db_driver() == 'pgsql') {
|
1045 |
|
|
$query->addExpression("array_to_string(array(SELECT DISTINCT pmia.type || '_' || pmia.recipient
|
1046 |
|
|
FROM {pm_index} pmia
|
1047 |
|
|
WHERE pmia.type <> 'hidden' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> :current), ',')", 'participants', array(':current' => $account->uid));
|
1048 |
|
|
}
|
1049 |
|
|
else {
|
1050 |
|
|
$query->addExpression("(SELECT GROUP_CONCAT(DISTINCT CONCAT(pmia.type, '_', pmia.recipient))
|
1051 |
|
|
FROM {pm_index} pmia
|
1052 |
|
|
WHERE pmia.type <> 'hidden' AND pmia.thread_id = pmi.thread_id AND pmia.recipient <> :current)", 'participants', array(':current' => $account->uid));
|
1053 |
|
|
}
|
1054 |
|
|
}
|
1055 |
|
|
if (in_array('thread_started', $fields)) {
|
1056 |
|
|
$query->addExpression('MIN(pm.timestamp)', 'thread_started');
|
1057 |
|
|
}
|
1058 |
|
|
return $query
|
1059 |
|
|
->condition('pmi.recipient', $account->uid)
|
1060 |
|
|
->condition('pmi.type', array('hidden', 'user'))
|
1061 |
|
|
->condition('pmi.deleted', 0)
|
1062 |
|
|
->groupBy('pmi.thread_id')
|
1063 |
|
|
->orderByHeader(_privatemsg_list_headers(array_merge(array('subject', 'last_updated'), $fields)))
|
1064 |
|
|
->limit(variable_get('privatemsg_per_page', 25));
|
1065 |
|
|
}
|
1066 |
|
|
|
1067 |
|
|
/**
|
1068 |
|
|
* Query definition to load messages of one or multiple threads.
|
1069 |
|
|
*
|
1070 |
|
|
* @param $threads
|
1071 |
|
|
* Array with one or multiple thread id's.
|
1072 |
|
|
* @param $account
|
1073 |
|
|
* User object for which the messages are being loaded.
|
1074 |
|
|
* @param $load_all
|
1075 |
|
|
* Deleted messages are only loaded if this is set to TRUE.
|
1076 |
|
|
*
|
1077 |
|
|
* @see hook_query_privatemsg_messages_alter()
|
1078 |
|
|
*/
|
1079 |
|
|
function privatemsg_sql_messages($threads, $account = NULL, $load_all = FALSE) {
|
1080 |
|
|
$query = db_select('pm_index', 'pmi');
|
1081 |
|
|
$query->addField('pmi', 'mid');
|
1082 |
|
|
$query->join('pm_message', 'pm', 'pm.mid = pmi.mid');
|
1083 |
|
|
if (!$load_all) {
|
1084 |
|
|
$query->condition('pmi.deleted', 0);
|
1085 |
|
|
}
|
1086 |
|
|
// If there are multiple inserts during the same second (tests, for example)
|
1087 |
|
|
// sort by mid second to have them in the same order as they were saved.
|
1088 |
|
|
$query
|
1089 |
|
|
->condition('pmi.thread_id', $threads)
|
1090 |
|
|
->groupBy('pm.timestamp')
|
1091 |
|
|
->groupBy('pmi.mid')
|
1092 |
|
|
// Order by timestamp first.
|
1093 |
|
|
->orderBy('pm.timestamp', 'ASC')
|
1094 |
|
|
// If there are multiple inserts during the same second (tests, for example)
|
1095 |
|
|
// sort by mid second to have them in the same order as they were saved.
|
1096 |
|
|
->orderBy('pmi.mid', 'ASC');
|
1097 |
|
|
if ($account) {
|
1098 |
|
|
$query
|
1099 |
|
|
->condition('pmi.recipient', $account->uid)
|
1100 |
|
|
->condition('pmi.type', array('hidden', 'user'));
|
1101 |
|
|
}
|
1102 |
|
|
return $query;
|
1103 |
|
|
}
|
1104 |
|
|
|
1105 |
|
|
/**
|
1106 |
|
|
* Load all participants of a thread.
|
1107 |
|
|
*
|
1108 |
|
|
* @param $thread_id
|
1109 |
|
|
* Thread id from which the participants should be loaded.
|
1110 |
|
|
* @param $account
|
1111 |
|
|
* User account that should be considered when loading participants.
|
1112 |
|
|
*
|
1113 |
|
|
* @see hook_query_privatemsg_participants_alter()
|
1114 |
|
|
*/
|
1115 |
|
|
function privatemsg_sql_participants($thread_id, $account = NULL) {
|
1116 |
|
|
$query = db_select('pm_index', 'pmi');
|
1117 |
|
|
$query->leftJoin('users', 'u', "u.uid = pmi.recipient AND pmi.type IN ('user', 'hidden')");
|
1118 |
|
|
$query
|
1119 |
|
|
->fields('pmi', array('recipient', 'type'))
|
1120 |
|
|
->fields('u', array('name'))
|
1121 |
|
|
->condition('pmi.thread_id', $thread_id);
|
1122 |
|
|
|
1123 |
|
|
// If an account is provided, limit participants.
|
1124 |
|
|
if ($account) {
|
1125 |
|
|
$query->condition(db_or()
|
1126 |
|
|
->condition('pmi.type', 'hidden', '<>')
|
1127 |
|
|
->condition(db_and()
|
1128 |
|
|
->condition('pmi.type', 'hidden')
|
1129 |
|
|
->condition('pmi.recipient', $account->uid)
|
1130 |
|
|
));
|
1131 |
|
|
|
1132 |
|
|
// Only load recipients of messages which are visible for that user.
|
1133 |
|
|
$query->where('(SELECT 1 FROM {pm_index} pmiu WHERE pmi.mid = pmiu.mid AND pmiu.recipient = :recipient LIMIT 1) = 1', array(':recipient' => $account->uid));
|
1134 |
|
|
}
|
1135 |
|
|
else {
|
1136 |
|
|
// If not, only limit participants to visible ones.
|
1137 |
|
|
$query->condition('pmi.type', 'hidden', '<>');
|
1138 |
|
|
}
|
1139 |
|
|
|
1140 |
|
|
return $query
|
1141 |
|
|
->groupBy('pmi.recipient')
|
1142 |
|
|
->groupBy('u.name')
|
1143 |
|
|
->groupBy('pmi.type');
|
1144 |
|
|
}
|
1145 |
|
|
|
1146 |
|
|
/**
|
1147 |
|
|
* Count threads with unread messages.
|
1148 |
|
|
*
|
1149 |
|
|
* @param $account
|
1150 |
|
|
* User account for which should be checked.
|
1151 |
|
|
*
|
1152 |
|
|
* @see hook_query_privatemsg_unread_count_alter()
|
1153 |
|
|
*/
|
1154 |
|
|
function privatemsg_sql_unread_count($account) {
|
1155 |
|
|
$query = db_select('pm_index', 'pmi');
|
1156 |
|
|
$query->addExpression('COUNT(DISTINCT thread_id)', 'unread_count');
|
1157 |
|
|
return $query
|
1158 |
|
|
->condition('pmi.deleted', 0)
|
1159 |
|
|
->condition('pmi.is_new', 1)
|
1160 |
|
|
->condition('pmi.recipient', $account->uid)
|
1161 |
|
|
->condition('pmi.type', array('hidden', 'user'));
|
1162 |
|
|
}
|
1163 |
|
|
|
1164 |
|
|
/**
|
1165 |
|
|
* Looks up autocomplete suggestions for users.
|
1166 |
|
|
*
|
1167 |
|
|
* @param $search
|
1168 |
|
|
* The string that is being searched for.
|
1169 |
|
|
* @param $names
|
1170 |
|
|
* Array of names which are already selected and should be excluded.
|
1171 |
|
|
*
|
1172 |
|
|
* @see hook_query_privatemsg_autocomplete_alter()
|
1173 |
|
|
*/
|
1174 |
|
|
function privatemsg_sql_autocomplete($search, $names) {
|
1175 |
|
|
$query = db_select('users', 'u')
|
1176 |
|
|
->fields('u', array('uid'))
|
1177 |
|
|
->condition('u.name', $search . '%', 'LIKE')
|
1178 |
|
|
->condition('u.status', 0, '<>')
|
1179 |
|
|
->where('NOT EXISTS (SELECT 1 FROM {pm_disable} pd WHERE pd.uid=u.uid)')
|
1180 |
|
|
->orderBy('u.name', 'ASC')
|
1181 |
|
|
->range(0, 10);
|
1182 |
|
|
|
1183 |
|
|
if (!empty($names)) {
|
1184 |
|
|
$query->condition('u.name', $names, 'NOT IN');
|
1185 |
|
|
}
|
1186 |
|
|
return $query;
|
1187 |
|
|
}
|
1188 |
|
|
|
1189 |
|
|
/**
|
1190 |
|
|
* Query Builder function to load all messages that should be flushed.
|
1191 |
|
|
*
|
1192 |
|
|
* @param $days
|
1193 |
|
|
* Select messages older than x days.
|
1194 |
|
|
* @param $max
|
1195 |
|
|
* Select no more than $max messages.
|
1196 |
|
|
*
|
1197 |
|
|
* @see hook_query_privatemsg_deleted_alter()
|
1198 |
|
|
*/
|
1199 |
|
|
function privatemsg_sql_deleted($days, $max) {
|
1200 |
|
|
$query = db_select('pm_message', 'pm');
|
1201 |
|
|
$query->addField('pm', 'mid');
|
1202 |
|
|
|
1203 |
|
|
$query->join('pm_index', 'pmi', 'pmi.mid = pm.mid');
|
1204 |
|
|
return $query
|
1205 |
|
|
->groupBy('pm.mid')
|
1206 |
|
|
->having('MIN(pmi.deleted) > 0 AND MAX(pmi.deleted) < :old', array(':old' => REQUEST_TIME - $days * 86400))
|
1207 |
|
|
->range(0, $max);
|
1208 |
|
|
}
|
1209 |
|
|
|
1210 |
|
|
/**
|
1211 |
|
|
* @}
|
1212 |
|
|
*/
|
1213 |
|
|
|
1214 |
|
|
/**
|
1215 |
|
|
* Implements hook_user_view().
|
1216 |
|
|
*/
|
1217 |
|
|
function privatemsg_user_view($account) {
|
1218 |
|
|
if (($url = privatemsg_get_link(array($account))) && variable_get('privatemsg_display_profile_links', 1)) {
|
1219 |
|
|
$account->content['privatemsg_send_new_message'] = array(
|
1220 |
|
|
'#type' => 'link',
|
1221 |
|
|
'#title' => t('Send this user a private message'),
|
1222 |
|
|
'#href' => $url,
|
1223 |
|
|
'#weight' => 10,
|
1224 |
|
|
'#options' => array(
|
1225 |
|
|
'query' => drupal_get_destination(),
|
1226 |
|
|
'title' => t('Send this user a private message'),
|
1227 |
|
|
'attributes' => array('class' => 'privatemsg-send-link privatemsg-send-link-profile'),
|
1228 |
|
|
),
|
1229 |
|
|
);
|
1230 |
|
|
}
|
1231 |
|
|
}
|
1232 |
|
|
|
1233 |
|
|
/**
|
1234 |
|
|
* Implements hook_user_login().
|
1235 |
|
|
*/
|
1236 |
|
|
function privatemsg_user_login(&$edit, $account) {
|
1237 |
|
|
if (variable_get('privatemsg_display_loginmessage', TRUE) && privatemsg_user_access()) {
|
1238 |
|
|
$count = privatemsg_unread_count();
|
1239 |
|
|
if ($count) {
|
1240 |
|
|
drupal_set_message(format_plural($count, 'You have <a href="@messages">1 unread message</a>.', 'You have <a href="@messages">@count unread messages</a>', array('@messages' => url('messages'))));
|
1241 |
|
|
}
|
1242 |
|
|
}
|
1243 |
|
|
}
|
1244 |
|
|
|
1245 |
|
|
/**
|
1246 |
|
|
* Implements hook_user_cancel().
|
1247 |
|
|
*/
|
1248 |
|
|
function privatemsg_user_cancel($edit, $account, $method) {
|
1249 |
|
|
switch ($method) {
|
1250 |
|
|
case 'user_cancel_reassign':
|
1251 |
|
|
db_update('pm_message')
|
1252 |
|
|
->condition('author', $account->uid)
|
1253 |
|
|
->fields(array('author' => 0))
|
1254 |
|
|
->execute();
|
1255 |
|
|
break;
|
1256 |
|
|
case 'user_cancel_block_unpublish':
|
1257 |
|
|
_privatemsg_delete_data($account);
|
1258 |
|
|
break;
|
1259 |
|
|
}
|
1260 |
|
|
// Always delete user settings.
|
1261 |
|
|
db_delete('pm_disable')
|
1262 |
|
|
->condition('uid', $account->uid)
|
1263 |
|
|
->execute();
|
1264 |
|
|
}
|
1265 |
|
|
|
1266 |
|
|
/**
|
1267 |
|
|
* Implements hook_user_delete().
|
1268 |
|
|
*/
|
1269 |
|
|
function privatemsg_user_delete($account) {
|
1270 |
|
|
_privatemsg_delete_data($account);
|
1271 |
|
|
// Always delete user settings.
|
1272 |
|
|
db_delete('pm_disable')
|
1273 |
|
|
->condition('uid', $account->uid)
|
1274 |
|
|
->execute();
|
1275 |
|
|
}
|
1276 |
|
|
|
1277 |
|
|
/**
|
1278 |
|
|
* Delete all message data from a user.
|
1279 |
|
|
*/
|
1280 |
|
|
function _privatemsg_delete_data($account) {
|
1281 |
|
|
$mids = db_select('pm_message', 'pm')
|
1282 |
|
|
->fields('pm', array('mid'))
|
1283 |
|
|
->condition('author', $account->uid)
|
1284 |
|
|
->execute()
|
1285 |
|
|
->fetchCol();
|
1286 |
|
|
|
1287 |
|
|
if (!empty($mids)) {
|
1288 |
|
|
// Delete recipient entries in {pm_index} of the messages the user wrote.
|
1289 |
|
|
db_delete('pm_index')
|
1290 |
|
|
->condition('mid', $mids)
|
1291 |
|
|
->execute();
|
1292 |
|
|
}
|
1293 |
|
|
|
1294 |
|
|
// Delete messages the user wrote.
|
1295 |
|
|
db_delete('pm_message')
|
1296 |
|
|
->condition('author', $account->uid)
|
1297 |
|
|
->execute();
|
1298 |
|
|
|
1299 |
|
|
// Delete recipient entries of that user.
|
1300 |
|
|
db_delete('pm_index')
|
1301 |
|
|
->condition('recipient', $account->uid)
|
1302 |
|
|
->condition('type', array('user', 'hidden'))
|
1303 |
|
|
->execute();
|
1304 |
|
|
|
1305 |
|
|
// DELETE any disable flag for user.
|
1306 |
|
|
db_delete('pm_disable')
|
1307 |
|
|
->condition('uid', $account->uid)
|
1308 |
|
|
->execute();
|
1309 |
|
|
}
|
1310 |
|
|
|
1311 |
|
|
/**
|
1312 |
|
|
* Implements hook_form_alter().
|
1313 |
|
|
*/
|
1314 |
|
|
function privatemsg_form_alter(&$form, &$form_state, $form_id) {
|
1315 |
|
|
if (($form_id == 'user_register_form' || $form_id == 'user_profile_form') && $form['#user_category'] == 'account') {
|
1316 |
|
|
|
1317 |
|
|
// Create array to be able to merge in fieldset and avoid overwriting
|
1318 |
|
|
// already added options.
|
1319 |
|
|
if (!isset($form['privatemsg'])) {
|
1320 |
|
|
$form['privatemsg'] = array();
|
1321 |
|
|
}
|
1322 |
|
|
|
1323 |
|
|
// Always create the fieldset in case other modules want to add
|
1324 |
|
|
// Privatemsg-related settings through hook_form_alter(). If it's still
|
1325 |
|
|
// empty after the build process, the after build function will remove it.
|
1326 |
|
|
$form['privatemsg'] += array(
|
1327 |
|
|
'#type' => 'fieldset',
|
1328 |
|
|
'#title' => t('Private messages'),
|
1329 |
|
|
'#collapsible' => TRUE,
|
1330 |
|
|
'#collapsed' => FALSE,
|
1331 |
|
|
'#weight' => 10,
|
1332 |
|
|
'#after_build' => array('privatemsg_account_fieldset_remove_if_empty'),
|
1333 |
|
|
);
|
1334 |
|
|
|
1335 |
|
|
// We have to use user_access() because privatemsg_user_access() would
|
1336 |
|
|
// return FALSE when privatemsg is disabled.
|
1337 |
|
|
if ((user_access('write privatemsg') || user_access('read privatemsg')) && user_access('allow disabling privatemsg')) {
|
1338 |
|
|
$form['privatemsg']['pm_enable'] = array(
|
1339 |
|
|
'#type' => 'checkbox',
|
1340 |
|
|
'#title' => t('Enable private messages'),
|
1341 |
|
|
'#default_value' => !privatemsg_is_disabled($form['#user']),
|
1342 |
|
|
'#description' => t('Disabling private messages prevents you from sending or receiving messages from other users.'),
|
1343 |
|
|
'#weight' => -10,
|
1344 |
|
|
);
|
1345 |
|
|
}
|
1346 |
|
|
}
|
1347 |
|
|
}
|
1348 |
|
|
|
1349 |
|
|
/**
|
1350 |
|
|
* Hides the settings fieldset if there are no options to be displayed.
|
1351 |
|
|
*/
|
1352 |
|
|
function privatemsg_account_fieldset_remove_if_empty($element) {
|
1353 |
|
|
if (count(element_children($element)) == 0) {
|
1354 |
|
|
$element['#access'] = FALSE;
|
1355 |
|
|
}
|
1356 |
|
|
return $element;
|
1357 |
|
|
}
|
1358 |
|
|
|
1359 |
|
|
/**
|
1360 |
|
|
* Implements hook_user_insert().
|
1361 |
|
|
*/
|
1362 |
|
|
function privatemsg_user_update(&$edit, $account, $category) {
|
1363 |
|
|
if (isset($edit['pm_enable']) && (user_access('write privatemsg') || user_access('read privatemsg')) && user_access('allow disabling privatemsg')) {
|
1364 |
|
|
$current = privatemsg_is_disabled($account);
|
1365 |
|
|
$disabled = (!$edit['pm_enable']);
|
1366 |
|
|
$edit['pm_enable'] = NULL;
|
1367 |
|
|
|
1368 |
|
|
$account->privatemsg_disabled = $disabled;
|
1369 |
|
|
|
1370 |
|
|
// only perform the save if the value has changed
|
1371 |
|
|
if ($current != $disabled) {
|
1372 |
|
|
if ($disabled) {
|
1373 |
|
|
db_insert('pm_disable')
|
1374 |
|
|
->fields(array('uid' => $account->uid))
|
1375 |
|
|
->execute();
|
1376 |
|
|
}
|
1377 |
|
|
else {
|
1378 |
|
|
db_delete('pm_disable')
|
1379 |
|
|
->condition('uid', $account->uid)
|
1380 |
|
|
->execute();
|
1381 |
|
|
}
|
1382 |
|
|
}
|
1383 |
|
|
}
|
1384 |
|
|
}
|
1385 |
|
|
|
1386 |
|
|
/**
|
1387 |
|
|
* Implements hook_privatemsg_block_message().
|
1388 |
|
|
*/
|
1389 |
|
|
function privatemsg_privatemsg_block_message($author, $recipients, $context = array()) {
|
1390 |
|
|
$blocked = array();
|
1391 |
|
|
if (privatemsg_is_disabled($author)) {
|
1392 |
|
|
$blocked[] = array(
|
1393 |
|
|
'recipient' => 'user_' . $author->uid,
|
1394 |
|
|
'message' => t('You have disabled private message sending and receiving.'),
|
1395 |
|
|
);
|
1396 |
|
|
}
|
1397 |
|
|
foreach ($recipients as $recipient) {
|
1398 |
|
|
if (privatemsg_is_disabled($recipient)) {
|
1399 |
|
|
$blocked[] = array(
|
1400 |
|
|
'recipient' => 'user_' . $recipient->uid,
|
1401 |
|
|
'message' => t('%recipient has disabled private message receiving.', array('%recipient' => privatemsg_recipient_format($recipient, array('plain' => TRUE)))),
|
1402 |
|
|
);
|
1403 |
|
|
}
|
1404 |
|
|
// Do not send private messages to blocked users.
|
1405 |
|
|
else if (isset($recipient->status) && (!$recipient->status)) {
|
1406 |
|
|
$blocked[] = array(
|
1407 |
|
|
'recipient' => 'user_' . $recipient->uid,
|
1408 |
|
|
'message' => t('%recipient has disabled his or her account.', array('%recipient' => privatemsg_recipient_format($recipient, array('plain' => TRUE)))),
|
1409 |
|
|
);
|
1410 |
|
|
}
|
1411 |
|
|
}
|
1412 |
|
|
|
1413 |
|
|
return $blocked;
|
1414 |
|
|
}
|
1415 |
|
|
|
1416 |
|
|
/**
|
1417 |
|
|
* Implements hook_block_info().
|
1418 |
|
|
*/
|
1419 |
|
|
function privatemsg_block_info() {
|
1420 |
|
|
$blocks = array();
|
1421 |
|
|
$blocks['privatemsg-menu'] = array(
|
1422 |
|
|
'info' => t('Privatemsg links'),
|
1423 |
|
|
'cache' => DRUPAL_NO_CACHE,
|
1424 |
|
|
);
|
1425 |
|
|
$blocks['privatemsg-new'] = array(
|
1426 |
|
|
'info' => t('New message indication'),
|
1427 |
|
|
'cache' => DRUPAL_NO_CACHE,
|
1428 |
|
|
);
|
1429 |
|
|
|
1430 |
|
|
return $blocks;
|
1431 |
|
|
}
|
1432 |
|
|
|
1433 |
|
|
/**
|
1434 |
|
|
* Implements hook_block_view().
|
1435 |
|
|
*/
|
1436 |
|
|
function privatemsg_block_view($delta) {
|
1437 |
|
|
$block = array();
|
1438 |
|
|
switch ($delta) {
|
1439 |
|
|
case 'privatemsg-menu':
|
1440 |
|
|
$block = _privatemsg_block_menu();
|
1441 |
|
|
break;
|
1442 |
|
|
case 'privatemsg-new':
|
1443 |
|
|
$block = _privatemsg_block_new();
|
1444 |
|
|
break;
|
1445 |
|
|
}
|
1446 |
|
|
return $block;
|
1447 |
|
|
}
|
1448 |
|
|
|
1449 |
|
|
/**
|
1450 |
|
|
* Implements hook_block_configure().
|
1451 |
|
|
*/
|
1452 |
|
|
function privatemsg_block_configure($delta = '') {
|
1453 |
|
|
if ($delta == 'privatemsg-new') {
|
1454 |
|
|
$form['notification'] = array(
|
1455 |
|
|
'#type' => 'checkbox',
|
1456 |
|
|
'#title' => t('Display block when there are no new messages'),
|
1457 |
|
|
'#default_value' => variable_get('privatemsg_no_messages_notification', 0),
|
1458 |
|
|
'#description' => t('Enable this to have this block always displayed, even if there are no new messages'),
|
1459 |
|
|
);
|
1460 |
|
|
return $form;
|
1461 |
|
|
}
|
1462 |
|
|
}
|
1463 |
|
|
|
1464 |
|
|
/**
|
1465 |
|
|
* Implements hook_block_save().
|
1466 |
|
|
*/
|
1467 |
|
|
function privatemsg_block_save($delta = '', $edit = array()) {
|
1468 |
|
|
if ($delta == 'privatemsg-new') {
|
1469 |
|
|
variable_set('privatemsg_no_messages_notification', $edit['notification']);
|
1470 |
|
|
}
|
1471 |
|
|
}
|
1472 |
|
|
|
1473 |
|
|
function privatemsg_title_callback($title = NULL) {
|
1474 |
|
|
$count = privatemsg_unread_count();
|
1475 |
|
|
|
1476 |
|
|
if ($count > 0) {
|
1477 |
|
|
return format_plural($count, 'Messages (1 new)', 'Messages (@count new)');
|
1478 |
|
|
}
|
1479 |
|
|
return t('Messages');
|
1480 |
|
|
}
|
1481 |
|
|
|
1482 |
|
|
|
1483 |
|
|
function _privatemsg_block_new() {
|
1484 |
|
|
$block = array();
|
1485 |
|
|
|
1486 |
|
|
if (!privatemsg_user_access()) {
|
1487 |
|
|
return $block;
|
1488 |
|
|
}
|
1489 |
|
|
|
1490 |
|
|
$count = privatemsg_unread_count();
|
1491 |
|
|
if ($count || variable_get('privatemsg_no_messages_notification', 0)) {
|
1492 |
|
|
$block = array(
|
1493 |
|
|
'subject' => $count ? format_plural($count, 'New message', 'New messages') : t('No new messages'),
|
1494 |
|
|
'content' => theme('privatemsg_new_block', array('count' => $count)),
|
1495 |
|
|
);
|
1496 |
|
|
return $block;
|
1497 |
|
|
}
|
1498 |
|
|
return array();
|
1499 |
|
|
}
|
1500 |
|
|
|
1501 |
|
|
function _privatemsg_block_menu() {
|
1502 |
|
|
$block = array();
|
1503 |
|
|
|
1504 |
|
|
$links = array();
|
1505 |
|
|
if (privatemsg_user_access('write privatemsg')) {
|
1506 |
|
|
$links[] = l(t('Write new message'), 'messages/new');
|
1507 |
|
|
}
|
1508 |
|
|
if (privatemsg_user_access('read privatemsg') || privatemsg_user_access('read all private messages') ) {
|
1509 |
|
|
$links[] = l(privatemsg_title_callback(), 'messages');
|
1510 |
|
|
}
|
1511 |
|
|
if (count($links)) {
|
1512 |
|
|
$block = array(
|
1513 |
|
|
'subject' => t('Private messages'),
|
1514 |
|
|
'content' => theme('item_list', array('items' => $links)),
|
1515 |
|
|
);
|
1516 |
|
|
}
|
1517 |
|
|
return $block;
|
1518 |
|
|
}
|
1519 |
|
|
|
1520 |
|
|
/**
|
1521 |
|
|
* Delete or restore a message.
|
1522 |
|
|
*
|
1523 |
|
|
* @param $pmid
|
1524 |
|
|
* Message id, pm.mid field.
|
1525 |
|
|
* @param $delete
|
1526 |
|
|
* Either deletes or restores the thread (1 => delete, 0 => restore)
|
1527 |
|
|
* @param $account
|
1528 |
|
|
* User account for which the delete action should be carried out.
|
1529 |
|
|
* Set to NULL to delete for all users.
|
1530 |
|
|
*
|
1531 |
|
|
* @ingroup api
|
1532 |
|
|
*/
|
1533 |
|
|
function privatemsg_message_change_delete($pmid, $delete, $account = NULL) {
|
1534 |
|
|
$delete_value = 0;
|
1535 |
|
|
if ($delete == TRUE) {
|
1536 |
|
|
$delete_value = REQUEST_TIME;
|
1537 |
|
|
}
|
1538 |
|
|
|
1539 |
|
|
$update = db_update('pm_index')
|
1540 |
|
|
->fields(array('deleted' => $delete_value))
|
1541 |
|
|
->condition('mid', $pmid);
|
1542 |
|
|
if ($account) {
|
1543 |
|
|
$update
|
1544 |
|
|
->condition('recipient', $account->uid)
|
1545 |
|
|
->condition('type', array('user', 'hidden'));
|
1546 |
|
|
}
|
1547 |
|
|
$update->execute();
|
1548 |
|
|
}
|
1549 |
|
|
|
1550 |
|
|
/**
|
1551 |
|
|
* Send a new message.
|
1552 |
|
|
*
|
1553 |
|
|
* This functions does send a message in a new thread.
|
1554 |
|
|
* Example:
|
1555 |
|
|
* @code
|
1556 |
|
|
* privatemsg_new_thread(array(user_load(5)), 'The subject', 'The body text');
|
1557 |
|
|
* @endcode
|
1558 |
|
|
*
|
1559 |
|
|
* @param $recipients
|
1560 |
|
|
* Array of recipients (user objects)
|
1561 |
|
|
* @param $subject
|
1562 |
|
|
* The subject of the new message
|
1563 |
|
|
* @param $body
|
1564 |
|
|
* The body text of the new message
|
1565 |
|
|
* @param $options
|
1566 |
|
|
* Additional options, possible keys:
|
1567 |
|
|
* author => User object of the author
|
1568 |
|
|
* timestamp => Time when the message was sent
|
1569 |
|
|
*
|
1570 |
|
|
* @return
|
1571 |
|
|
* An array with a key success. If TRUE, it also contains a key 'message' with
|
1572 |
|
|
* the created $message array, the same that is passed to the insert hook.
|
1573 |
|
|
* If FALSE, it contains a key 'messages'. This key contains an array where
|
1574 |
|
|
* the key is the error type (error, warning, notice) and an array with
|
1575 |
|
|
* messages of that type.
|
1576 |
|
|
*
|
1577 |
|
|
* It is theoretically possible for success to be TRUE and message to be FALSE.
|
1578 |
|
|
* For example if one of the privatemsg database tables become corrupted. When testing
|
1579 |
|
|
* for success of message being sent it is always best to see if ['message'] is not FALSE
|
1580 |
|
|
* as well as ['success'] is TRUE.
|
1581 |
|
|
*
|
1582 |
|
|
* Example:
|
1583 |
|
|
* @code
|
1584 |
|
|
* array('error' => array('A error message'))
|
1585 |
|
|
* @endcode
|
1586 |
|
|
*
|
1587 |
|
|
* @ingroup api
|
1588 |
|
|
*/
|
1589 |
|
|
function privatemsg_new_thread($recipients, $subject, $body = NULL, $options = array()) {
|
1590 |
|
|
global $user;
|
1591 |
|
|
$author = clone $user;
|
1592 |
|
|
|
1593 |
|
|
$message = (object)$options;
|
1594 |
|
|
$message->subject = $subject;
|
1595 |
|
|
$message->body = $body;
|
1596 |
|
|
// Make sure that recipients are keyed correctly and are not added
|
1597 |
|
|
// multiple times.
|
1598 |
|
|
foreach ($recipients as $recipient) {
|
1599 |
|
|
if (!isset($recipient->type)) {
|
1600 |
|
|
$recipient->type = 'user';
|
1601 |
|
|
$recipient->recipient = $recipient->uid;
|
1602 |
|
|
}
|
1603 |
|
|
$message->recipients[privatemsg_recipient_key($recipient)] = $recipient;
|
1604 |
|
|
}
|
1605 |
|
|
|
1606 |
|
|
// Apply defaults - this will not overwrite existing keys.
|
1607 |
|
|
if (!isset($message->author)) {
|
1608 |
|
|
$message->author = $author;
|
1609 |
|
|
}
|
1610 |
|
|
if (!isset($message->timestamp)) {
|
1611 |
|
|
$message->timestamp = REQUEST_TIME;
|
1612 |
|
|
}
|
1613 |
|
|
if (!isset($message->format)) {
|
1614 |
|
|
$message->format = filter_default_format($message->author);
|
1615 |
|
|
}
|
1616 |
|
|
|
1617 |
|
|
$validated = _privatemsg_validate_message($message);
|
1618 |
|
|
if ($validated['success']) {
|
1619 |
|
|
$validated['message'] = _privatemsg_send($message);
|
1620 |
|
|
if ($validated['message'] !== FALSE) {
|
1621 |
|
|
_privatemsg_handle_recipients($validated['message']->mid, $validated['message']->recipients, FALSE);
|
1622 |
|
|
}
|
1623 |
|
|
}
|
1624 |
|
|
|
1625 |
|
|
return $validated;
|
1626 |
|
|
}
|
1627 |
|
|
|
1628 |
|
|
/**
|
1629 |
|
|
* Send a reply message
|
1630 |
|
|
*
|
1631 |
|
|
* This functions replies on an existing thread.
|
1632 |
|
|
*
|
1633 |
|
|
* @param $thread_id
|
1634 |
|
|
* Thread id
|
1635 |
|
|
* @param $body
|
1636 |
|
|
* The body text of the new message
|
1637 |
|
|
* @param $options
|
1638 |
|
|
* Additional options, possible keys:
|
1639 |
|
|
* author => User object of the author
|
1640 |
|
|
* timestamp => Time when the message was sent
|
1641 |
|
|
*
|
1642 |
|
|
* @return
|
1643 |
|
|
* An array with a key success and messages. This key contains an array where
|
1644 |
|
|
* the key is the error type (error, warning, notice) and an array with
|
1645 |
|
|
* messages of that type.. If success is TRUE, it also contains a key $message
|
1646 |
|
|
* with the created $message array, the same that is passed to
|
1647 |
|
|
* hook_privatemsg_message_insert().
|
1648 |
|
|
*
|
1649 |
|
|
* It is theoretically possible for success to be TRUE and message to be FALSE.
|
1650 |
|
|
* For example if one of the privatemsg database tables become corrupted. When testing
|
1651 |
|
|
* for success of message being sent it is always best to see if ['message'] is not FALSE
|
1652 |
|
|
* as well as ['success'] is TRUE.
|
1653 |
|
|
*
|
1654 |
|
|
* Example messages values:
|
1655 |
|
|
* @code
|
1656 |
|
|
* array('error' => array('A error message'))
|
1657 |
|
|
* @endcode
|
1658 |
|
|
*
|
1659 |
|
|
* @ingroup api
|
1660 |
|
|
*/
|
1661 |
|
|
function privatemsg_reply($thread_id, $body, $options = array()) {
|
1662 |
|
|
global $user;
|
1663 |
|
|
$author = clone $user;
|
1664 |
|
|
|
1665 |
|
|
$message = (object)$options;
|
1666 |
|
|
$message->body = $body;
|
1667 |
|
|
|
1668 |
|
|
// Apply defaults - this will not overwrite existing keys.
|
1669 |
|
|
if (!isset($message->author)) {
|
1670 |
|
|
$message->author = $author;
|
1671 |
|
|
}
|
1672 |
|
|
if (!isset($message->timestamp)) {
|
1673 |
|
|
$message->timestamp = REQUEST_TIME;
|
1674 |
|
|
}
|
1675 |
|
|
if (!isset($message->format)) {
|
1676 |
|
|
$message->format = filter_default_format($message->author);
|
1677 |
|
|
}
|
1678 |
|
|
|
1679 |
|
|
// We don't know the subject and the recipients, so we need to load them..
|
1680 |
|
|
// thread_id == mid on the first message of the thread
|
1681 |
|
|
$first_message = privatemsg_message_load($thread_id, $message->author);
|
1682 |
|
|
if (!$first_message) {
|
1683 |
|
|
return array(
|
1684 |
|
|
'success' => FALSE,
|
1685 |
|
|
'messages' => array('error' => array(t('Thread %thread_id not found, unable to answer', array('%thread_id' => $thread_id)))),
|
1686 |
|
|
);
|
1687 |
|
|
}
|
1688 |
|
|
|
1689 |
|
|
$message->thread_id = $thread_id;
|
1690 |
|
|
// Load participants
|
1691 |
|
|
$message->recipients = _privatemsg_load_thread_participants($thread_id, $message->author);
|
1692 |
|
|
$message->subject = $first_message->subject;
|
1693 |
|
|
$validated = _privatemsg_validate_message($message);
|
1694 |
|
|
if ($validated['success']) {
|
1695 |
|
|
$validated['message'] = _privatemsg_send($message);
|
1696 |
|
|
if ($validated['message'] !== FALSE) {
|
1697 |
|
|
_privatemsg_handle_recipients($validated['message']->mid, $validated['message']->recipients, FALSE);
|
1698 |
|
|
}
|
1699 |
|
|
}
|
1700 |
|
|
return $validated;
|
1701 |
|
|
}
|
1702 |
|
|
|
1703 |
|
|
function _privatemsg_validate_message(&$message, $form = FALSE) {
|
1704 |
|
|
$messages = array('error' => array(), 'warning' => array());
|
1705 |
|
|
if (!(privatemsg_user_access('write privatemsg', $message->author) || (privatemsg_user_access('reply only privatemsg', $message->author) && isset($message->thread_id)))) {
|
1706 |
|
|
// no need to do further checks in this case...
|
1707 |
|
|
if ($form) {
|
1708 |
|
|
form_set_error('author', t('You are not allowed to write messages.'));
|
1709 |
|
|
return array(
|
1710 |
|
|
'success' => FALSE,
|
1711 |
|
|
'messages' => $messages,
|
1712 |
|
|
);
|
1713 |
|
|
}
|
1714 |
|
|
else {
|
1715 |
|
|
$messages['error'][] = t('@user is not allowed to write messages.', array('@user' => privatemsg_recipient_format($message->author, array('plain' => TRUE))));
|
1716 |
|
|
return array(
|
1717 |
|
|
'success' => FALSE,
|
1718 |
|
|
'messages' => $messages,
|
1719 |
|
|
);
|
1720 |
|
|
}
|
1721 |
|
|
}
|
1722 |
|
|
|
1723 |
|
|
// Prevent subjects which only consist of a space as these can not be clicked.
|
1724 |
|
|
$message->subject = trim($message->subject);
|
1725 |
|
|
if (empty($message->subject)) {
|
1726 |
|
|
if ($form) {
|
1727 |
|
|
form_set_error('subject', t('You must include a subject line with your message.'));
|
1728 |
|
|
}
|
1729 |
|
|
else {
|
1730 |
|
|
$messages['error'][] = t('A subject must be included with the message.');
|
1731 |
|
|
}
|
1732 |
|
|
}
|
1733 |
|
|
|
1734 |
|
|
// Don't allow replies without a body.
|
1735 |
|
|
if (!empty($message->thread_id) && ($message->body === NULL || $message->body === '') ) {
|
1736 |
|
|
if ($form) {
|
1737 |
|
|
form_set_error('body', t('You must include a message in your reply.'));
|
1738 |
|
|
}
|
1739 |
|
|
else {
|
1740 |
|
|
$messages['error'][] = t('A message must be included in your reply.');
|
1741 |
|
|
}
|
1742 |
|
|
}
|
1743 |
|
|
|
1744 |
|
|
// Check if an allowed format is used.
|
1745 |
|
|
if (!filter_access(filter_format_load($message->format), $message->author)) {
|
1746 |
|
|
if ($form) {
|
1747 |
|
|
form_set_error('format', t('You are not allowed to use the specified format.'));
|
1748 |
|
|
}
|
1749 |
|
|
else {
|
1750 |
|
|
$messages['error'][] = t('@user is not allowed to use the specified input format.', array('@user' => privatemsg_recipient_format($message->author, array('plain' => TRUE))));
|
1751 |
|
|
}
|
1752 |
|
|
}
|
1753 |
|
|
|
1754 |
|
|
if (empty($message->recipients) || !is_array($message->recipients)) {
|
1755 |
|
|
if ($form) {
|
1756 |
|
|
form_set_error('recipient', t('You must include at least one valid recipient.'));
|
1757 |
|
|
}
|
1758 |
|
|
else {
|
1759 |
|
|
$messages['error'][] = t('At least one valid recipient must be included with the message.');
|
1760 |
|
|
}
|
1761 |
|
|
}
|
1762 |
|
|
|
1763 |
|
|
if (!empty($message->recipients) && is_array($message->recipients)) {
|
1764 |
|
|
foreach (module_invoke_all('privatemsg_block_message', $message->author, $message->recipients, (array)$message) as $blocked) {
|
1765 |
|
|
unset($message->recipients[$blocked['recipient']]);
|
1766 |
|
|
if ($form) {
|
1767 |
|
|
drupal_set_message($blocked['message'], 'warning');
|
1768 |
|
|
}
|
1769 |
|
|
else {
|
1770 |
|
|
$messages['warning'][] = $blocked['message'];
|
1771 |
|
|
}
|
1772 |
|
|
}
|
1773 |
|
|
}
|
1774 |
|
|
|
1775 |
|
|
// Check again, give another error message if all recipients are blocked
|
1776 |
|
|
if (empty($message->recipients)) {
|
1777 |
|
|
if ($form) {
|
1778 |
|
|
form_set_error('recipient', t('You are not allowed to send this message because all recipients are blocked.'));
|
1779 |
|
|
}
|
1780 |
|
|
else {
|
1781 |
|
|
$messages['error'][] = t('The message cannot be sent because all recipients are blocked.');
|
1782 |
|
|
}
|
1783 |
|
|
}
|
1784 |
|
|
|
1785 |
|
|
// Verify if message has tokens and user is allowed to use them.
|
1786 |
|
|
$message->has_tokens = privatemsg_user_access('use tokens in privatemsg', $message->author) && count(token_scan($message->subject . $message->body));
|
1787 |
|
|
|
1788 |
|
|
$messages = array_merge_recursive(module_invoke_all('privatemsg_message_validate', $message, $form), $messages);
|
1789 |
|
|
|
1790 |
|
|
// Check if there are errors in $messages or if $form is TRUE, there are form errors.
|
1791 |
|
|
$success = empty($messages['error']) || ($form && count((array)form_get_errors()) > 0);
|
1792 |
|
|
return array(
|
1793 |
|
|
'success' => $success,
|
1794 |
|
|
'messages' => $messages,
|
1795 |
|
|
);
|
1796 |
|
|
}
|
1797 |
|
|
|
1798 |
|
|
/**
|
1799 |
|
|
* Internal function to save a message.
|
1800 |
|
|
*
|
1801 |
|
|
* @param $message
|
1802 |
|
|
* A $message array with the data that should be saved. If a thread_id exists
|
1803 |
|
|
* it will be created as a reply to an existing thread. If not, a new thread
|
1804 |
|
|
* will be created.
|
1805 |
|
|
*
|
1806 |
|
|
* @return
|
1807 |
|
|
* The updated $message array.
|
1808 |
|
|
*/
|
1809 |
|
|
function _privatemsg_send($message) {
|
1810 |
|
|
$transaction = db_transaction();
|
1811 |
|
|
try {
|
1812 |
|
|
drupal_alter('privatemsg_message_presave', $message);
|
1813 |
|
|
field_attach_presave('privatemsg_message', $message);
|
1814 |
|
|
|
1815 |
|
|
$query = db_insert('pm_index')->fields(array('mid', 'thread_id', 'recipient', 'type', 'is_new', 'deleted'));
|
1816 |
|
|
if (isset($message->read_all) && $message->read_all && isset($message->thread_id)) {
|
1817 |
|
|
// The message was sent in read all mode, add the author as recipient to all
|
1818 |
|
|
// existing messages.
|
1819 |
|
|
$query_messages = _privatemsg_assemble_query('messages', array($message->thread_id), NULL);
|
1820 |
|
|
foreach ($query_messages->execute()->fetchCol() as $mid) {
|
1821 |
|
|
$query->values(array(
|
1822 |
|
|
'mid' => $mid,
|
1823 |
|
|
'thread_id' => $message->thread_id,
|
1824 |
|
|
'recipient' => $message->author->uid,
|
1825 |
|
|
'type' => 'user',
|
1826 |
|
|
'is_new' => 0,
|
1827 |
|
|
'deleted' => 0,
|
1828 |
|
|
));
|
1829 |
|
|
}
|
1830 |
|
|
}
|
1831 |
|
|
|
1832 |
|
|
// 1) Save the message body first.
|
1833 |
|
|
$args = array();
|
1834 |
|
|
$args['subject'] = $message->subject;
|
1835 |
|
|
$args['author'] = $message->author->uid;
|
1836 |
|
|
$args['body'] = $message->body;
|
1837 |
|
|
$args['format'] = $message->format;
|
1838 |
|
|
$args['timestamp'] = $message->timestamp;
|
1839 |
|
|
$args['has_tokens'] = (int)$message->has_tokens;
|
1840 |
|
|
$mid = db_insert('pm_message')
|
1841 |
|
|
->fields($args)
|
1842 |
|
|
->execute();
|
1843 |
|
|
$message->mid = $mid;
|
1844 |
|
|
|
1845 |
|
|
// Thread ID is the same as the mid if it's the first message in the thread.
|
1846 |
|
|
if (!isset($message->thread_id)) {
|
1847 |
|
|
$message->thread_id = $mid;
|
1848 |
|
|
}
|
1849 |
|
|
|
1850 |
|
|
// 2) Save message to recipients.
|
1851 |
|
|
// Each recipient gets a record in the pm_index table.
|
1852 |
|
|
foreach ($message->recipients as $recipient) {
|
1853 |
|
|
$query->values(array(
|
1854 |
|
|
'mid' => $mid,
|
1855 |
|
|
'thread_id' => $message->thread_id,
|
1856 |
|
|
'recipient' => $recipient->recipient,
|
1857 |
|
|
'type' => $recipient->type,
|
1858 |
|
|
'is_new' => 1,
|
1859 |
|
|
'deleted' => 0,
|
1860 |
|
|
));
|
1861 |
|
|
}
|
1862 |
|
|
|
1863 |
|
|
// We only want to add the author to the pm_index table, if the message has
|
1864 |
|
|
// not been sent directly to him.
|
1865 |
|
|
if (!isset($message->recipients['user_' . $message->author->uid])) {
|
1866 |
|
|
$query->values(array(
|
1867 |
|
|
'mid' => $mid,
|
1868 |
|
|
'thread_id' => $message->thread_id,
|
1869 |
|
|
'recipient' => $message->author->uid,
|
1870 |
|
|
'type' => 'user',
|
1871 |
|
|
'is_new' => 0,
|
1872 |
|
|
'deleted' => 0,
|
1873 |
|
|
));
|
1874 |
|
|
}
|
1875 |
|
|
$query->execute();
|
1876 |
|
|
|
1877 |
|
|
module_invoke_all('privatemsg_message_insert', $message);
|
1878 |
|
|
field_attach_insert('privatemsg_message', $message);
|
1879 |
|
|
|
1880 |
|
|
} catch (Exception $exception) {
|
1881 |
|
|
$transaction->rollback();
|
1882 |
|
|
watchdog_exception('privatemsg', $exception);
|
1883 |
|
|
throw $exception;
|
1884 |
|
|
}
|
1885 |
|
|
|
1886 |
|
|
// If we reached here that means we were successful at writing all messages to db.
|
1887 |
|
|
return $message;
|
1888 |
|
|
}
|
1889 |
|
|
|
1890 |
|
|
/**
|
1891 |
|
|
* Returns a link to send message form for a specific users.
|
1892 |
|
|
*
|
1893 |
|
|
* Contains permission checks of author/recipient, blocking and
|
1894 |
|
|
* if a anonymous user is involved.
|
1895 |
|
|
*
|
1896 |
|
|
* @param $recipient
|
1897 |
|
|
* Recipient of the message
|
1898 |
|
|
* @param $account
|
1899 |
|
|
* Sender of the message, defaults to the current user
|
1900 |
|
|
*
|
1901 |
|
|
* @return
|
1902 |
|
|
* Either FALSE or a URL string
|
1903 |
|
|
*
|
1904 |
|
|
* @ingroup api
|
1905 |
|
|
*/
|
1906 |
|
|
function privatemsg_get_link($recipients, $account = array(), $subject = NULL) {
|
1907 |
|
|
if ($account == NULL) {
|
1908 |
|
|
global $user;
|
1909 |
|
|
$account = $user;
|
1910 |
|
|
}
|
1911 |
|
|
|
1912 |
|
|
if (!is_array($recipients)) {
|
1913 |
|
|
$recipients = array($recipients);
|
1914 |
|
|
}
|
1915 |
|
|
|
1916 |
|
|
if (!privatemsg_user_access('write privatemsg', $account) || $account->uid == 0) {
|
1917 |
|
|
return FALSE;
|
1918 |
|
|
}
|
1919 |
|
|
|
1920 |
|
|
$validated = array();
|
1921 |
|
|
foreach ($recipients as $recipient) {
|
1922 |
|
|
if (!privatemsg_user_access('read privatemsg', $recipient)) {
|
1923 |
|
|
continue;
|
1924 |
|
|
}
|
1925 |
|
|
if (variable_get('privatemsg_display_link_self', TRUE) == FALSE && $account->uid == $recipient->uid) {
|
1926 |
|
|
continue;
|
1927 |
|
|
}
|
1928 |
|
|
if (count(module_invoke_all('privatemsg_block_message', $account, array(privatemsg_recipient_key($recipient) => $recipient))) > 0) {
|
1929 |
|
|
continue;
|
1930 |
|
|
}
|
1931 |
|
|
$validated[] = $recipient->uid;
|
1932 |
|
|
}
|
1933 |
|
|
if (empty($validated)) {
|
1934 |
|
|
return FALSE;
|
1935 |
|
|
}
|
1936 |
|
|
$url = 'messages/new/' . implode(',', $validated);
|
1937 |
|
|
if (!is_null($subject)) {
|
1938 |
|
|
// Explicitly encode the / so that it will be encoded twice to work around
|
1939 |
|
|
// the the menu_system.
|
1940 |
|
|
$url .= '/' . str_replace('/', '%2F', $subject);
|
1941 |
|
|
}
|
1942 |
|
|
return $url;
|
1943 |
|
|
}
|
1944 |
|
|
|
1945 |
|
|
/**
|
1946 |
|
|
* Load a single message.
|
1947 |
|
|
*
|
1948 |
|
|
* @param $pmid
|
1949 |
|
|
* Message id, pm.mid field
|
1950 |
|
|
* @param $account
|
1951 |
|
|
* For which account the message should be loaded.
|
1952 |
|
|
* Defaults to the current user.
|
1953 |
|
|
*
|
1954 |
|
|
* @ingroup api
|
1955 |
|
|
*/
|
1956 |
|
|
function privatemsg_message_load($pmid, $account = NULL) {
|
1957 |
|
|
// If $pmid is object or array - do nothing
|
1958 |
|
|
// (fixing conflict with message_load() function in message module).
|
1959 |
|
|
if(is_array($pmid) || is_object($pmid)) {
|
1960 |
|
|
return NULL;
|
1961 |
|
|
}
|
1962 |
|
|
$conditions = array();
|
1963 |
|
|
if ($account) {
|
1964 |
|
|
$conditions['account'] = $account;
|
1965 |
|
|
}
|
1966 |
|
|
$messages = privatemsg_message_load_multiple(array($pmid), $conditions);
|
1967 |
|
|
return current($messages);
|
1968 |
|
|
}
|
1969 |
|
|
|
1970 |
|
|
/**
|
1971 |
|
|
* Load multiple messages.
|
1972 |
|
|
*
|
1973 |
|
|
* @param $pmids
|
1974 |
|
|
* Array of Message ids, pm.mid field
|
1975 |
|
|
* @param $account
|
1976 |
|
|
* For which account the message should be loaded.
|
1977 |
|
|
* Defaults to the current user.
|
1978 |
|
|
*
|
1979 |
|
|
* @ingroup api
|
1980 |
|
|
*/
|
1981 |
|
|
function privatemsg_message_load_multiple(array $pmids, array $conditions = array(), $reset = FALSE) {
|
1982 |
|
|
$result = entity_load('privatemsg_message', $pmids, $conditions, $reset);
|
1983 |
|
|
return $result;
|
1984 |
|
|
}
|
1985 |
|
|
|
1986 |
|
|
/**
|
1987 |
|
|
* Generates a query based on a query id.
|
1988 |
|
|
*
|
1989 |
|
|
* @param $query
|
1990 |
|
|
* Either be a string ('some_id') or an array('group_name', 'query_id'),
|
1991 |
|
|
* if a string is supplied, group_name defaults to 'privatemsg'.
|
1992 |
|
|
*
|
1993 |
|
|
* @return SelectQuery
|
1994 |
|
|
* Array with the keys query and count. count can be used to count the
|
1995 |
|
|
* elements which would be returned by query. count can be used together
|
1996 |
|
|
* with pager_query().
|
1997 |
|
|
*
|
1998 |
|
|
* @ingroup sql
|
1999 |
|
|
*/
|
2000 |
|
|
function _privatemsg_assemble_query($query) {
|
2001 |
|
|
|
2002 |
|
|
// Modules will be allowed to choose the prefix for the query builder,
|
2003 |
|
|
// but if there is not one supplied, 'privatemsg' will be taken by default.
|
2004 |
|
|
if (is_array($query)) {
|
2005 |
|
|
$query_id = $query[0];
|
2006 |
|
|
$query_group = $query[1];
|
2007 |
|
|
}
|
2008 |
|
|
else {
|
2009 |
|
|
$query_id = $query;
|
2010 |
|
|
$query_group = 'privatemsg';
|
2011 |
|
|
}
|
2012 |
|
|
|
2013 |
|
|
/**
|
2014 |
|
|
* Begin: dynamic arguments
|
2015 |
|
|
*/
|
2016 |
|
|
$args = func_get_args();
|
2017 |
|
|
unset($args[0]);
|
2018 |
|
|
// We do the merge because we call call_user_func_array and not drupal_alter.
|
2019 |
|
|
// This is necessary because otherwise we would not be able to use $args
|
2020 |
|
|
// correctly (otherwise it doesn't unfold).
|
2021 |
|
|
$query_function = $query_group . '_sql_' . $query_id;
|
2022 |
|
|
if (!function_exists($query_function)) {
|
2023 |
|
|
drupal_set_message(t('Query function %function does not exist', array('%function' => $query_function)), 'error');
|
2024 |
|
|
return FALSE;
|
2025 |
|
|
}
|
2026 |
|
|
$query = call_user_func_array($query_function, $args);
|
2027 |
|
|
// Add a tag to make it alterable.
|
2028 |
|
|
$query->addTag($query_group . '_' . $query_id);
|
2029 |
|
|
|
2030 |
|
|
// Add arguments as metadata.
|
2031 |
|
|
foreach ($args as $id => $arg) {
|
2032 |
|
|
$query->addMetaData('arg_' . $id, $arg);
|
2033 |
|
|
}
|
2034 |
|
|
|
2035 |
|
|
return $query;
|
2036 |
|
|
}
|
2037 |
|
|
|
2038 |
|
|
/**
|
2039 |
|
|
* Marks one or multiple threads as (un)read.
|
2040 |
|
|
*
|
2041 |
|
|
* @param $threads
|
2042 |
|
|
* Array with thread id's or a single thread id.
|
2043 |
|
|
* @param $status
|
2044 |
|
|
* Either PRIVATEMSG_READ or PRIVATEMSG_UNREAD, sets the new status.
|
2045 |
|
|
* @param $account
|
2046 |
|
|
* User object for which the threads should be deleted, defaults to the current user.
|
2047 |
|
|
*/
|
2048 |
|
|
function privatemsg_thread_change_status($threads, $status, $account = NULL) {
|
2049 |
|
|
if (!is_array($threads)) {
|
2050 |
|
|
$threads = array($threads);
|
2051 |
|
|
}
|
2052 |
|
|
if (empty($account)) {
|
2053 |
|
|
global $user;
|
2054 |
|
|
$account = clone $user;
|
2055 |
|
|
}
|
2056 |
|
|
// Merge status and uid with the existing thread list.
|
2057 |
|
|
$params = array(
|
2058 |
|
|
':status' => $status,
|
2059 |
|
|
':recipient' => $account->uid,
|
2060 |
|
|
':threads' => $threads,
|
2061 |
|
|
);
|
2062 |
|
|
|
2063 |
|
|
// Record which messages will change status.
|
2064 |
|
|
$result = db_query("SELECT mid FROM {pm_index} WHERE is_new <> :status AND recipient = :recipient and type IN ('user', 'hidden') AND thread_id IN (:threads)", $params);
|
2065 |
|
|
$changed = $result->fetchCol();
|
2066 |
|
|
|
2067 |
|
|
// Update the status of the threads.
|
2068 |
|
|
db_update('pm_index')
|
2069 |
|
|
->fields(array('is_new' => $status))
|
2070 |
|
|
->condition('thread_id', $threads)
|
2071 |
|
|
->condition('recipient', $account->uid)
|
2072 |
|
|
->condition('type', array('user', 'hidden'))
|
2073 |
|
|
->execute();
|
2074 |
|
|
|
2075 |
|
|
// Allow modules to respond to the status changes.
|
2076 |
|
|
foreach ($changed as $mid) {
|
2077 |
|
|
module_invoke_all('privatemsg_message_status_changed', $mid, $status, $account);
|
2078 |
|
|
}
|
2079 |
|
|
|
2080 |
|
|
if ($status == PRIVATEMSG_UNREAD) {
|
2081 |
|
|
drupal_set_message(format_plural(count($threads), 'Marked 1 thread as unread.', 'Marked @count threads as unread.'));
|
2082 |
|
|
}
|
2083 |
|
|
else {
|
2084 |
|
|
drupal_set_message(format_plural(count($threads), 'Marked 1 thread as read.', 'Marked @count threads as read.'));
|
2085 |
|
|
}
|
2086 |
|
|
}
|
2087 |
|
|
/**
|
2088 |
|
|
* Returns a table header definition based on the submitted keys.
|
2089 |
|
|
*
|
2090 |
|
|
* Uses @link theming theme patterns @endlink to theme single headers.
|
2091 |
|
|
*
|
2092 |
|
|
* @param $has_posts
|
2093 |
|
|
* TRUE when there is at least one row. Decides if the select all checkbox should be displayed.
|
2094 |
|
|
* @param $keys
|
2095 |
|
|
* Array with the keys which are present in the query/should be displayed.
|
2096 |
|
|
* @return
|
2097 |
|
|
* Array with header definitions for tablesort_sql and theme('table').
|
2098 |
|
|
*/
|
2099 |
|
|
function _privatemsg_list_headers($keys) {
|
2100 |
|
|
|
2101 |
|
|
// theme() doesn't include the theme file for patterns, we need to do it manually.
|
2102 |
|
|
include_once drupal_get_path('module', 'privatemsg') . '/privatemsg.theme.inc';
|
2103 |
|
|
|
2104 |
|
|
$header = array();
|
2105 |
|
|
foreach ($keys as $key) {
|
2106 |
|
|
// First, try to load a specific theme for that header, if not present, use the default.
|
2107 |
|
|
if ($return = theme('privatemsg_list_header__' . $key)) {
|
2108 |
|
|
// The default theme returns nothing, only store the value if we have something.
|
2109 |
|
|
$header[$key] = $return;
|
2110 |
|
|
}
|
2111 |
|
|
}
|
2112 |
|
|
uasort($header, 'element_sort');
|
2113 |
|
|
// Remove weight column or it will show up in the markup.
|
2114 |
|
|
foreach ($header as $key => $element) {
|
2115 |
|
|
if (isset($header[$key]['#weight'])) {
|
2116 |
|
|
unset($header[$key]['#weight']);
|
2117 |
|
|
}
|
2118 |
|
|
}
|
2119 |
|
|
return $header;
|
2120 |
|
|
}
|
2121 |
|
|
|
2122 |
|
|
/**
|
2123 |
|
|
* Formats all rows (#options) in the privatemsg tableselect thread list.
|
2124 |
|
|
*
|
2125 |
|
|
* Uses @link theming theme patterns @endlink to theme single fields.
|
2126 |
|
|
*
|
2127 |
|
|
* @param $thread
|
2128 |
|
|
* Array with the row data returned by the database.
|
2129 |
|
|
* @return
|
2130 |
|
|
* Row definition for use with theme('table')
|
2131 |
|
|
*/
|
2132 |
|
|
function _privatemsg_list_thread($tableselect) {
|
2133 |
|
|
foreach ($tableselect['#options'] as $id => $thread) {
|
2134 |
|
|
$row = array();
|
2135 |
|
|
if (!empty($thread['is_new'])) {
|
2136 |
|
|
// Set the css class in the tr tag.
|
2137 |
|
|
$row['#attributes']['class'][] = 'privatemsg-unread';
|
2138 |
|
|
}
|
2139 |
|
|
foreach ($thread as $key => $data) {
|
2140 |
|
|
// First, try to load a specific theme for that field, if not present, use the default.
|
2141 |
|
|
if ($return = theme('privatemsg_list_field__' . $key, array('thread' => $thread))) {
|
2142 |
|
|
// The default theme returns nothing, only store the value if we have something.
|
2143 |
|
|
$row[$key] = $return;
|
2144 |
|
|
}
|
2145 |
|
|
}
|
2146 |
|
|
$tableselect['#options'][$id] = $row;
|
2147 |
|
|
}
|
2148 |
|
|
return $tableselect;
|
2149 |
|
|
}
|
2150 |
|
|
|
2151 |
|
|
/**
|
2152 |
|
|
* Execute an operation on a number of threads.
|
2153 |
|
|
*
|
2154 |
|
|
* @param $operation
|
2155 |
|
|
* The operation that should be executed.
|
2156 |
|
|
* @see hook_privatemsg_thread_operations()
|
2157 |
|
|
* @param $threads
|
2158 |
|
|
* An array of thread ids. The array is filtered before used, a checkboxes
|
2159 |
|
|
* array can be directly passed to it.
|
2160 |
|
|
*/
|
2161 |
|
|
function privatemsg_operation_execute($operation, $threads, $account = NULL) {
|
2162 |
|
|
// Filter out unchecked threads, this gives us an array of "checked" threads.
|
2163 |
|
|
$threads = array_filter($threads);
|
2164 |
|
|
|
2165 |
|
|
if (empty($threads)) {
|
2166 |
|
|
// Do not execute anything if there are no checked threads.
|
2167 |
|
|
drupal_set_message(t('You must first select one (or more) messages before you can take that action.'), 'warning');
|
2168 |
|
|
return FALSE;
|
2169 |
|
|
}
|
2170 |
|
|
// Add in callback arguments if present.
|
2171 |
|
|
if (isset($operation['callback arguments'])) {
|
2172 |
|
|
$args = array_merge(array($threads), $operation['callback arguments']);
|
2173 |
|
|
}
|
2174 |
|
|
else {
|
2175 |
|
|
$args = array($threads);
|
2176 |
|
|
}
|
2177 |
|
|
|
2178 |
|
|
// Add the user object to the arguments.
|
2179 |
|
|
if ($account) {
|
2180 |
|
|
$args[] = $account;
|
2181 |
|
|
}
|
2182 |
|
|
|
2183 |
|
|
// Execute the chosen action and pass the defined arguments.
|
2184 |
|
|
call_user_func_array($operation['callback'], $args);
|
2185 |
|
|
|
2186 |
|
|
if (!empty($operation['success message'])) {
|
2187 |
|
|
drupal_set_message($operation['success message']);
|
2188 |
|
|
}
|
2189 |
|
|
|
2190 |
|
|
// Check if that operation has defined an undo callback.
|
2191 |
|
|
if (isset($operation['undo callback']) && $undo_function = $operation['undo callback']) {
|
2192 |
|
|
// Add in callback arguments if present.
|
2193 |
|
|
if (isset($operation['undo callback arguments'])) {
|
2194 |
|
|
$undo_args = array_merge(array($threads), $operation['undo callback arguments']);
|
2195 |
|
|
}
|
2196 |
|
|
else {
|
2197 |
|
|
$undo_args = array($threads);
|
2198 |
|
|
}
|
2199 |
|
|
|
2200 |
|
|
// Avoid saving the complete user object in the session.
|
2201 |
|
|
if ($account) {
|
2202 |
|
|
$undo_args['account'] = $account->uid;
|
2203 |
|
|
}
|
2204 |
|
|
// Store the undo callback in the session and display a "Undo" link.
|
2205 |
|
|
// @todo: Provide a more flexible solution for such an undo action, operation defined string for example.
|
2206 |
|
|
$_SESSION['privatemsg']['undo callback'] = array('function' => $undo_function, 'args' => $undo_args);
|
2207 |
|
|
$undo = url('messages/undo/action', array('query' => drupal_get_destination()));
|
2208 |
|
|
|
2209 |
|
|
drupal_set_message(t('The previous action can be <a href="!undo">undone</a>.', array('!undo' => $undo)));
|
2210 |
|
|
}
|
2211 |
|
|
|
2212 |
|
|
// Allow modules to respond to the operation.
|
2213 |
|
|
module_invoke_all('privatemsg_operation_executed', $operation, $threads, $account);
|
2214 |
|
|
|
2215 |
|
|
return TRUE;
|
2216 |
|
|
}
|
2217 |
|
|
|
2218 |
|
|
/**
|
2219 |
|
|
* Delete or restore one or multiple threads.
|
2220 |
|
|
*
|
2221 |
|
|
* @param $threads
|
2222 |
|
|
* Array with thread id's or a single thread id.
|
2223 |
|
|
* @param $delete
|
2224 |
|
|
* Indicates if the threads should be deleted or restored. 1 => delete, 0 => restore.
|
2225 |
|
|
* @param $account
|
2226 |
|
|
* User account for which the delete action should be carried out - Set to NULL to delete for all users.
|
2227 |
|
|
*/
|
2228 |
|
|
function privatemsg_thread_change_delete($threads, $delete, $account = NULL) {
|
2229 |
|
|
if (!is_array($threads)) {
|
2230 |
|
|
$threads = array($threads);
|
2231 |
|
|
}
|
2232 |
|
|
if (empty($account)) {
|
2233 |
|
|
global $user;
|
2234 |
|
|
$account = clone $user;
|
2235 |
|
|
}
|
2236 |
|
|
|
2237 |
|
|
// Load all messages of those threads including the deleted.
|
2238 |
|
|
$query = _privatemsg_assemble_query('messages', $threads, $account, TRUE);
|
2239 |
|
|
|
2240 |
|
|
// Delete each message. We need to do that to trigger the delete hook.
|
2241 |
|
|
foreach ($query->execute() as $row) {
|
2242 |
|
|
privatemsg_message_change_delete($row->mid, $delete, $account);
|
2243 |
|
|
}
|
2244 |
|
|
|
2245 |
|
|
if ($delete) {
|
2246 |
|
|
drupal_set_message(format_plural(count($threads), 'Deleted 1 thread.', 'Deleted @count threads.'));
|
2247 |
|
|
}
|
2248 |
|
|
else {
|
2249 |
|
|
drupal_set_message(format_plural(count($threads), 'Restored 1 thread.', 'Restored @count threads.'));
|
2250 |
|
|
}
|
2251 |
|
|
}
|
2252 |
|
|
|
2253 |
|
|
/**
|
2254 |
|
|
* Implements hook_privatemsg_thread_operations().
|
2255 |
|
|
*/
|
2256 |
|
|
function privatemsg_privatemsg_thread_operations() {
|
2257 |
|
|
$operations = array(
|
2258 |
|
|
'mark as read' => array(
|
2259 |
|
|
'label' => t('Mark as read'),
|
2260 |
|
|
'callback' => 'privatemsg_thread_change_status',
|
2261 |
|
|
'callback arguments' => array('status' => PRIVATEMSG_READ),
|
2262 |
|
|
'undo callback' => 'privatemsg_thread_change_status',
|
2263 |
|
|
'undo callback arguments' => array('status' => PRIVATEMSG_UNREAD),
|
2264 |
|
|
),
|
2265 |
|
|
'mark as unread' => array(
|
2266 |
|
|
'label' => t('Mark as unread'),
|
2267 |
|
|
'callback' => 'privatemsg_thread_change_status',
|
2268 |
|
|
'callback arguments' => array('status' => PRIVATEMSG_UNREAD),
|
2269 |
|
|
'undo callback' => 'privatemsg_thread_change_status',
|
2270 |
|
|
'undo callback arguments' => array('status' => PRIVATEMSG_READ),
|
2271 |
|
|
),
|
2272 |
|
|
);
|
2273 |
|
|
if (privatemsg_user_access('delete privatemsg')) {
|
2274 |
|
|
$operations['delete'] = array(
|
2275 |
|
|
'label' => t('Delete'),
|
2276 |
|
|
'callback' => 'privatemsg_thread_change_delete',
|
2277 |
|
|
'callback arguments' => array('delete' => 1),
|
2278 |
|
|
'undo callback' => 'privatemsg_thread_change_delete',
|
2279 |
|
|
'undo callback arguments' => array('delete' => 0),
|
2280 |
|
|
'button' => TRUE,
|
2281 |
|
|
);
|
2282 |
|
|
}
|
2283 |
|
|
return $operations;
|
2284 |
|
|
}
|
2285 |
|
|
|
2286 |
|
|
/**
|
2287 |
|
|
* Implements hook_entity_info().
|
2288 |
|
|
*/
|
2289 |
|
|
function privatemsg_entity_info() {
|
2290 |
|
|
return array(
|
2291 |
|
|
'privatemsg_message' => array(
|
2292 |
|
|
'label' => t('Privatemsg'),
|
2293 |
|
|
'base table' => 'pm_message',
|
2294 |
|
|
'fieldable' => TRUE,
|
2295 |
|
|
'controller class' => 'PrivatemsgMessageController',
|
2296 |
|
|
'uri callback' => 'privatemsg_message_uri_callback',
|
2297 |
|
|
'entity keys' => array(
|
2298 |
|
|
'id' => 'mid',
|
2299 |
|
|
),
|
2300 |
|
|
'bundles' => array(
|
2301 |
|
|
'privatemsg_message' => array(
|
2302 |
|
|
'label' => t('Private message'),
|
2303 |
|
|
'admin' => array(
|
2304 |
|
|
'path' => 'admin/config/messaging/privatemsg',
|
2305 |
|
|
'access arguments' => array('administer privatemsg settings'),
|
2306 |
|
|
),
|
2307 |
|
|
),
|
2308 |
|
|
),
|
2309 |
|
|
),
|
2310 |
|
|
);
|
2311 |
|
|
}
|
2312 |
|
|
|
2313 |
|
|
/**
|
2314 |
|
|
* Returns the URI for a private message.
|
2315 |
|
|
*
|
2316 |
|
|
* @param $message
|
2317 |
|
|
* Private message object.
|
2318 |
|
|
*
|
2319 |
|
|
* @return
|
2320 |
|
|
* URI array as defined by hook_entity_info().
|
2321 |
|
|
*/
|
2322 |
|
|
function privatemsg_message_uri_callback($message) {
|
2323 |
|
|
$uri = array();
|
2324 |
|
|
if (isset($message->mid) && isset($message->thread_id)) {
|
2325 |
|
|
$uri = array(
|
2326 |
|
|
'path' => 'messages/view/' . $message->thread_id,
|
2327 |
|
|
'options' => array(),
|
2328 |
|
|
);
|
2329 |
|
|
// Add message fragment, if necessary.
|
2330 |
|
|
if ($message->mid != $message->thread_id) {
|
2331 |
|
|
$uri['options']['fragment'] = 'privatemsg-mid-' . $message->mid;
|
2332 |
|
|
}
|
2333 |
|
|
}
|
2334 |
|
|
return $uri;
|
2335 |
|
|
}
|
2336 |
|
|
|
2337 |
|
|
/**
|
2338 |
|
|
* Implements hook_build_modes().
|
2339 |
|
|
*/
|
2340 |
|
|
function privatemsg_build_modes($obj_type) {
|
2341 |
|
|
$modes = array();
|
2342 |
|
|
if ($obj_type == 'privatemsg_message') {
|
2343 |
|
|
$modes['message'] = t('Message');
|
2344 |
|
|
}
|
2345 |
|
|
return $modes;
|
2346 |
|
|
}
|
2347 |
|
|
|
2348 |
|
|
/**
|
2349 |
|
|
* Implements hook_node_view().
|
2350 |
|
|
*/
|
2351 |
|
|
function privatemsg_node_view($node, $view_mode) {
|
2352 |
|
|
$types = array_filter(variable_get('privatemsg_link_node_types', array()));
|
2353 |
|
|
if (in_array($node->type, $types) && ($view_mode == 'full' || (variable_get('privatemsg_display_on_teaser', 1) && $view_mode == 'teaser'))) {
|
2354 |
|
|
$url = privatemsg_get_link(user_load($node->uid));
|
2355 |
|
|
if (!empty($url)){
|
2356 |
|
|
$node->content['links']['#links']['privatemsg_link'] = array(
|
2357 |
|
|
'title' => t('Send author a message'),
|
2358 |
|
|
'href' => $url . '/' . t('Message regarding @node', array('@node' => $node->title)),
|
2359 |
|
|
'query' => drupal_get_destination(),
|
2360 |
|
|
'attributes' => array('class' => 'privatemsg-send-link privatemsg-send-link-node'),
|
2361 |
|
|
);
|
2362 |
|
|
}
|
2363 |
|
|
}
|
2364 |
|
|
}
|
2365 |
|
|
|
2366 |
|
|
/**
|
2367 |
|
|
* Implements hook_comment_view().
|
2368 |
|
|
*/
|
2369 |
|
|
function privatemsg_comment_view($comment) {
|
2370 |
|
|
$types = array_filter(variable_get('privatemsg_link_node_types', array()));
|
2371 |
|
|
if (in_array(node_load($comment->nid)->type, $types) && variable_get('privatemsg_display_on_comments', 0)) {
|
2372 |
|
|
$url = privatemsg_get_link(user_load($comment->uid));
|
2373 |
|
|
if (!empty($url)){
|
2374 |
|
|
$links['privatemsg_link'] = array(
|
2375 |
|
|
'title' => t('Send author a message'),
|
2376 |
|
|
'href' => $url . '/' . t('Message regarding @comment', array('@comment' => trim($comment->subject))),
|
2377 |
|
|
'query' => drupal_get_destination(),
|
2378 |
|
|
);
|
2379 |
|
|
$comment->content['links']['privatemsg'] = array(
|
2380 |
|
|
'#theme' => 'links',
|
2381 |
|
|
'#links' => $links,
|
2382 |
|
|
'#attributes' => array('class' => array('privatemsg-send-link', 'privatemsg-send-link-node', 'links', 'inline')),
|
2383 |
|
|
);
|
2384 |
|
|
}
|
2385 |
|
|
}
|
2386 |
|
|
}
|
2387 |
|
|
/**
|
2388 |
|
|
* Implements hook_views_api().
|
2389 |
|
|
*/
|
2390 |
|
|
function privatemsg_views_api() {
|
2391 |
|
|
return array(
|
2392 |
|
|
'api' => 2,
|
2393 |
|
|
'path' => drupal_get_path('module', 'privatemsg') . '/views',
|
2394 |
|
|
);
|
2395 |
|
|
}
|
2396 |
|
|
|
2397 |
|
|
/**
|
2398 |
|
|
* Privatemsg wrapper function for user_load_multiple().
|
2399 |
|
|
*
|
2400 |
|
|
* The function adds the privatemsg specific recipient id (uid)
|
2401 |
|
|
* and recipient type to the user object.
|
2402 |
|
|
*
|
2403 |
|
|
* @param $uid
|
2404 |
|
|
* Which uid to load. Can either be a single id or an array of uids.
|
2405 |
|
|
* @return
|
2406 |
|
|
* If existing for the passed in uid, the user object with the recipient
|
2407 |
|
|
* and type properties.
|
2408 |
|
|
*/
|
2409 |
|
|
function privatemsg_user_load_multiple($uids) {
|
2410 |
|
|
$accounts = array();
|
2411 |
|
|
foreach (user_load_multiple($uids) as $account) {
|
2412 |
|
|
$account->recipient = $account->uid;
|
2413 |
|
|
$account->type = 'user';
|
2414 |
|
|
$accounts[privatemsg_recipient_key($account)] = $account;
|
2415 |
|
|
}
|
2416 |
|
|
return $accounts;
|
2417 |
|
|
}
|
2418 |
|
|
|
2419 |
|
|
/**
|
2420 |
|
|
* Return key for a recipient object used for arrays.
|
2421 |
|
|
* @param $recipient
|
2422 |
|
|
* Recipient object, must have type and recipient properties.
|
2423 |
|
|
* @return
|
2424 |
|
|
* A string that looks like type_id.
|
2425 |
|
|
*
|
2426 |
|
|
* @ingroup types
|
2427 |
|
|
*/
|
2428 |
|
|
function privatemsg_recipient_key($recipient) {
|
2429 |
|
|
if (empty($recipient->type)) {
|
2430 |
|
|
return 'user_' . $recipient->uid;
|
2431 |
|
|
}
|
2432 |
|
|
return $recipient->type . '_' . $recipient->recipient;
|
2433 |
|
|
}
|
2434 |
|
|
|
2435 |
|
|
/**
|
2436 |
|
|
* Returns an array of defined recipient types.
|
2437 |
|
|
*
|
2438 |
|
|
* @return
|
2439 |
|
|
* Array of recipient types
|
2440 |
|
|
* @see hook_privatemsg_recipient_type_info()
|
2441 |
|
|
*
|
2442 |
|
|
* @ingroup types
|
2443 |
|
|
*/
|
2444 |
|
|
function privatemsg_recipient_get_types() {
|
2445 |
|
|
$types = &drupal_static(__FUNCTION__, NULL);
|
2446 |
|
|
|
2447 |
|
|
if ($types === NULL) {
|
2448 |
|
|
$types = module_invoke_all('privatemsg_recipient_type_info');
|
2449 |
|
|
if (!is_array($types)) {
|
2450 |
|
|
$types = array();
|
2451 |
|
|
}
|
2452 |
|
|
drupal_alter('privatemsg_recipient_type_info', $types);
|
2453 |
|
|
uasort($types, 'element_sort');
|
2454 |
|
|
}
|
2455 |
|
|
return $types;
|
2456 |
|
|
}
|
2457 |
|
|
/**
|
2458 |
|
|
* Return a single recipient type information.
|
2459 |
|
|
* @param $type
|
2460 |
|
|
* Name of the recipient type.
|
2461 |
|
|
* @return
|
2462 |
|
|
* Array with the recipient type definition. NULL if the type doesn't exist.
|
2463 |
|
|
*
|
2464 |
|
|
* @ingroup types
|
2465 |
|
|
*/
|
2466 |
|
|
function privatemsg_recipient_get_type($type) {
|
2467 |
|
|
$types = privatemsg_recipient_get_types();
|
2468 |
|
|
if (!is_string($type)) {
|
2469 |
|
|
exit;
|
2470 |
|
|
}
|
2471 |
|
|
if (isset($types[$type])) {
|
2472 |
|
|
return $types[$type];
|
2473 |
|
|
}
|
2474 |
|
|
}
|
2475 |
|
|
|
2476 |
|
|
/**
|
2477 |
|
|
* Add or remove a recipient to an existing message.
|
2478 |
|
|
*
|
2479 |
|
|
* @param $mid
|
2480 |
|
|
* Message id for which the recipient should be added.
|
2481 |
|
|
* @param $recipient
|
2482 |
|
|
* Recipient id that should be added, for example uid.
|
2483 |
|
|
* @param $type
|
2484 |
|
|
* Type of the recipient, defaults to hidden.
|
2485 |
|
|
* @param $add
|
2486 |
|
|
* If TRUE, adds the recipient, if FALSE, removes it.
|
2487 |
|
|
*/
|
2488 |
|
|
function privatemsg_message_change_recipient($mid, $uid, $type = 'user', $add = TRUE) {
|
2489 |
|
|
// The message is statically cached, so only a single load is necessary.
|
2490 |
|
|
$message = privatemsg_message_load($mid);
|
2491 |
|
|
$thread_id = $message->thread_id;
|
2492 |
|
|
if ($add) {
|
2493 |
|
|
// Only add the recipient if he does not block the author.
|
2494 |
|
|
$recipient = user_load($uid);
|
2495 |
|
|
$context = ($thread_id == $mid) ? array() : array('thread_id' => $thread_id);
|
2496 |
|
|
$user_blocked = module_invoke_all('privatemsg_block_message', $message->author, array(privatemsg_recipient_key($recipient) => $recipient), $context);
|
2497 |
|
|
if (count($user_blocked) <> 0) {
|
2498 |
|
|
return;
|
2499 |
|
|
}
|
2500 |
|
|
|
2501 |
|
|
// Make sure to only add a recipient once. The types user and hidden are
|
2502 |
|
|
// considered equal here.
|
2503 |
|
|
$query = db_select('pm_index', 'pmi');
|
2504 |
|
|
$query->addExpression('1');
|
2505 |
|
|
$exists = $query
|
2506 |
|
|
->condition('mid', $mid)
|
2507 |
|
|
->condition('recipient', $uid)
|
2508 |
|
|
->condition('type', ($type == 'user' || $type == 'hidden') ? array('user', 'hidden') : $type)
|
2509 |
|
|
->execute()
|
2510 |
|
|
->fetchField();
|
2511 |
|
|
if (!$exists) {
|
2512 |
|
|
db_insert('pm_index')
|
2513 |
|
|
->fields(array(
|
2514 |
|
|
'mid' => $mid,
|
2515 |
|
|
'thread_id' => $thread_id,
|
2516 |
|
|
'recipient' => $uid,
|
2517 |
|
|
'type' => $type,
|
2518 |
|
|
'is_new' => 1,
|
2519 |
|
|
'deleted' => 0,
|
2520 |
|
|
))
|
2521 |
|
|
->execute();
|
2522 |
|
|
}
|
2523 |
|
|
}
|
2524 |
|
|
else {
|
2525 |
|
|
db_delete('pm_index')
|
2526 |
|
|
>condition('mid', $mid)
|
2527 |
|
|
->condition('recipient', $uid)
|
2528 |
|
|
->condition('type', $type)
|
2529 |
|
|
->execute();
|
2530 |
|
|
}
|
2531 |
|
|
module_invoke_all('privatemsg_message_recipient_changed', $mid, $thread_id, $uid, $type, $add);
|
2532 |
|
|
}
|
2533 |
|
|
|
2534 |
|
|
/**
|
2535 |
|
|
* Handle the non-user recipients of a new message.
|
2536 |
|
|
*
|
2537 |
|
|
* Either process them directly if they have less than a certain amount of users
|
2538 |
|
|
* or, if enabled, add them to a batch.
|
2539 |
|
|
*
|
2540 |
|
|
* @param $mid
|
2541 |
|
|
* Message id for which the recipients are processed.
|
2542 |
|
|
* @param $recipients
|
2543 |
|
|
* Array of recipients.
|
2544 |
|
|
* @param $use_batch
|
2545 |
|
|
* Use batch API to process recipients.
|
2546 |
|
|
*/
|
2547 |
|
|
function _privatemsg_handle_recipients($mid, $recipients, $use_batch = TRUE) {
|
2548 |
|
|
$batch = array(
|
2549 |
|
|
'title' => t('Processing recipients'),
|
2550 |
|
|
'operations' => array(),
|
2551 |
|
|
'file' => drupal_get_path('module', 'privatemsg') . '/privatemsg.pages.inc',
|
2552 |
|
|
'progress_message' => t('Processing recipients'),
|
2553 |
|
|
);
|
2554 |
|
|
$small_threshold = variable_get('privatemsg_recipient_small_threshold', 100);
|
2555 |
|
|
foreach ($recipients as $recipient) {
|
2556 |
|
|
// Add a batch operation to press non-user recipient types.
|
2557 |
|
|
if ($recipient->type != 'user' && $recipient->type != 'hidden') {
|
2558 |
|
|
$type = privatemsg_recipient_get_type($recipient->type);
|
2559 |
|
|
// Count the recipients, if there are less than small_threshold, process
|
2560 |
|
|
// them right now.
|
2561 |
|
|
$count_function = $type['count'];
|
2562 |
|
|
if (!is_callable($count_function)) {
|
2563 |
|
|
db_update('pm_index')
|
2564 |
|
|
->fields(array('is_new' => PRIVATEMSG_READ))
|
2565 |
|
|
->condition('mid', $mid)
|
2566 |
|
|
->condition('recipient', $recipient->recipient)
|
2567 |
|
|
->condition('type', $recipient->type)
|
2568 |
|
|
->execute();
|
2569 |
|
|
drupal_set_message(t('Recipient type %type is not correctly implemented', array('%type' => $recipient->type)), 'error');
|
2570 |
|
|
continue;
|
2571 |
|
|
}
|
2572 |
|
|
$count = $count_function($recipient);
|
2573 |
|
|
if ($count < $small_threshold) {
|
2574 |
|
|
$load_function = $type['generate recipients'];
|
2575 |
|
|
if (!is_callable($load_function)) {
|
2576 |
|
|
db_update('pm_index')
|
2577 |
|
|
->fields(array('is_new' => PRIVATEMSG_READ))
|
2578 |
|
|
->condition('mid', $mid)
|
2579 |
|
|
->condition('recipient', $recipient->recipient)
|
2580 |
|
|
->condition('type', $recipient->type)
|
2581 |
|
|
->execute();
|
2582 |
|
|
drupal_set_message(t('Recipient type %type is not correctly implemented', array('%type' => $recipient->type)), 'error');
|
2583 |
|
|
continue;
|
2584 |
|
|
}
|
2585 |
|
|
$uids = $load_function($recipient, $small_threshold, 0);
|
2586 |
|
|
if (!empty($uids)) {
|
2587 |
|
|
foreach ($uids as $uid) {
|
2588 |
|
|
privatemsg_message_change_recipient($mid, $uid, 'hidden');
|
2589 |
|
|
}
|
2590 |
|
|
}
|
2591 |
|
|
db_update('pm_index')
|
2592 |
|
|
->fields(array('is_new' => PRIVATEMSG_READ))
|
2593 |
|
|
->condition('mid', $mid)
|
2594 |
|
|
->condition('recipient', $recipient->recipient)
|
2595 |
|
|
->condition('type', $recipient->type)
|
2596 |
|
|
->execute();
|
2597 |
|
|
continue;
|
2598 |
|
|
}
|
2599 |
|
|
if ($use_batch) {
|
2600 |
|
|
$batch['operations'][] = array('privatemsg_load_recipients', array($mid, $recipient));
|
2601 |
|
|
}
|
2602 |
|
|
}
|
2603 |
|
|
}
|
2604 |
|
|
|
2605 |
|
|
// Set batch if there are outstanding operations.
|
2606 |
|
|
if ($use_batch && !empty($batch['operations'])) {
|
2607 |
|
|
batch_set($batch);
|
2608 |
|
|
}
|
2609 |
|
|
}
|
2610 |
|
|
|
2611 |
|
|
/**
|
2612 |
|
|
* This function is used to test if the current user has write/view access
|
2613 |
|
|
* for a specific recipient type.
|
2614 |
|
|
*
|
2615 |
|
|
* @param $type_name
|
2616 |
|
|
* The name of the recipient type.
|
2617 |
|
|
* @param $permission
|
2618 |
|
|
* Which permission should be checked: 'write' or 'view'.
|
2619 |
|
|
* @param $recipient
|
2620 |
|
|
* Optionally pass in a recipient for which the permission should be checked.
|
2621 |
|
|
* This only has effect if a the recipient type defines a callback function
|
2622 |
|
|
* and is simply passed through in that case.
|
2623 |
|
|
*
|
2624 |
|
|
* @return
|
2625 |
|
|
* TRUE if the user has that permission (or not permission is defined) and
|
2626 |
|
|
* FALSE if not.
|
2627 |
|
|
*
|
2628 |
|
|
* @ingroup types
|
2629 |
|
|
*/
|
2630 |
|
|
function privatemsg_recipient_access($type_name, $permission, $recipient = NULL) {
|
2631 |
|
|
if (($type = privatemsg_recipient_get_type($type_name))) {
|
2632 |
|
|
// First check if a callback function is defined.
|
2633 |
|
|
if (!empty($type[$permission . ' callback']) && is_callable($type[$permission . ' callback'])) {
|
2634 |
|
|
$callback = $type[$permission . ' callback'];
|
2635 |
|
|
return $callback($recipient);
|
2636 |
|
|
}
|
2637 |
|
|
|
2638 |
|
|
if (isset($type[$permission . ' access'])) {
|
2639 |
|
|
if (is_bool($type[$permission . ' access'])) {
|
2640 |
|
|
return $type[$permission . ' access'];
|
2641 |
|
|
}
|
2642 |
|
|
return user_access($type[$permission . ' access']);
|
2643 |
|
|
}
|
2644 |
|
|
}
|
2645 |
|
|
// If no access permission is defined, access is allowed.
|
2646 |
|
|
return TRUE;
|
2647 |
|
|
}
|
2648 |
|
|
|
2649 |
|
|
/**
|
2650 |
|
|
* Format a single participant.
|
2651 |
|
|
*
|
2652 |
|
|
* @param $participant
|
2653 |
|
|
* The participant object to format.
|
2654 |
|
|
*
|
2655 |
|
|
* @ingroup types.
|
2656 |
|
|
*/
|
2657 |
|
|
function privatemsg_recipient_format($recipient, $options = array()) {
|
2658 |
|
|
if (!isset($recipient->type)) {
|
2659 |
|
|
$recipient->type = 'user';
|
2660 |
|
|
$recipient->recipient = $recipient->uid;
|
2661 |
|
|
}
|
2662 |
|
|
$type = privatemsg_recipient_get_type($recipient->type);
|
2663 |
|
|
if (isset($type['format'])) {
|
2664 |
|
|
return theme($type['format'], array('recipient' => $recipient, 'options' => $options));
|
2665 |
|
|
}
|
2666 |
|
|
return NULL;
|
2667 |
|
|
}
|
2668 |
|
|
|
2669 |
|
|
/**
|
2670 |
|
|
* Implements hook_privatemsg_recipient_type_info().
|
2671 |
|
|
*/
|
2672 |
|
|
function privatemsg_privatemsg_recipient_type_info() {
|
2673 |
|
|
return array(
|
2674 |
|
|
'user' => array(
|
2675 |
|
|
'name' => t('User'),
|
2676 |
|
|
'description' => t('Enter a user name to write a message to a user.'),
|
2677 |
|
|
'load' => 'privatemsg_user_load_multiple',
|
2678 |
|
|
'format' => 'privatemsg_username',
|
2679 |
|
|
'autocomplete' => 'privatemsg_user_autocomplete',
|
2680 |
|
|
// Make sure this comes always last.
|
2681 |
|
|
'#weight' => 50,
|
2682 |
|
|
),
|
2683 |
|
|
);
|
2684 |
|
|
}
|
2685 |
|
|
|
2686 |
|
|
/**
|
2687 |
|
|
* Implements callback_recipient_autocomplete().
|
2688 |
|
|
*/
|
2689 |
|
|
function privatemsg_user_autocomplete($fragment, $names, $limit) {
|
2690 |
|
|
// First, load all possible uids.
|
2691 |
|
|
$uids = _privatemsg_assemble_query('autocomplete', $fragment, $names)
|
2692 |
|
|
->range(0, $limit)
|
2693 |
|
|
->execute()
|
2694 |
|
|
->fetchCol();
|
2695 |
|
|
$query = _privatemsg_assemble_query('autocomplete', $fragment, $names);
|
2696 |
|
|
$query->preExecute();
|
2697 |
|
|
$query->getArguments();
|
2698 |
|
|
// Load the corresponding users, make sure to not load any duplicates.
|
2699 |
|
|
$accounts = user_load_multiple(array_unique($uids));
|
2700 |
|
|
|
2701 |
|
|
// Return them in an array with the correct recipient key.
|
2702 |
|
|
$suggestions = array();
|
2703 |
|
|
foreach ($accounts as $account) {
|
2704 |
|
|
$account->type = 'user';
|
2705 |
|
|
$account->recipient = $account->uid;
|
2706 |
|
|
$suggestions[privatemsg_recipient_key($account)] = $account;
|
2707 |
|
|
}
|
2708 |
|
|
return $suggestions;
|
2709 |
|
|
|
2710 |
|
|
}
|
2711 |
|
|
|
2712 |
|
|
/**
|
2713 |
|
|
* Implements hook_field_extra_fields().
|
2714 |
|
|
*/
|
2715 |
|
|
function privatemsg_field_extra_fields() {
|
2716 |
|
|
$extra['user']['user'] = array(
|
2717 |
|
|
'form' => array(
|
2718 |
|
|
'privatemsg' => array(
|
2719 |
|
|
'label' => 'Private msg',
|
2720 |
|
|
'description' => t('Private messages'),
|
2721 |
|
|
'weight' => 5,
|
2722 |
|
|
),
|
2723 |
|
|
),
|
2724 |
|
|
'display' => array(
|
2725 |
|
|
'privatemsg_send_new_message' => array(
|
2726 |
|
|
'label' => 'Private msg',
|
2727 |
|
|
'description' => t('Private messages'),
|
2728 |
|
|
'weight' => 5,
|
2729 |
|
|
),
|
2730 |
|
|
),
|
2731 |
|
|
);
|
2732 |
|
|
$extra['privatemsg_message']['privatemsg_message'] = array(
|
2733 |
|
|
'form' => array(
|
2734 |
|
|
'recipient' => array(
|
2735 |
|
|
'label' => t('To'),
|
2736 |
|
|
'description' => t('Recipient field'),
|
2737 |
|
|
'weight' => -10,
|
2738 |
|
|
),
|
2739 |
|
|
'subject' => array(
|
2740 |
|
|
'label' => t('Message subject'),
|
2741 |
|
|
'description' => t('Message subject'),
|
2742 |
|
|
'weight' => -5,
|
2743 |
|
|
),
|
2744 |
|
|
'body' => array(
|
2745 |
|
|
'label' => t('Message body'),
|
2746 |
|
|
'description' => t('Message body'),
|
2747 |
|
|
'weight' => -3,
|
2748 |
|
|
),
|
2749 |
|
|
'token' => array(
|
2750 |
|
|
'label' => t('Token browser'),
|
2751 |
|
|
'description' => t('Displays usable tokens in a table for those who are allowed to use tokens in private messages.'),
|
2752 |
|
|
'weight' => -1,
|
2753 |
|
|
),
|
2754 |
|
|
),
|
2755 |
|
|
'display' => array(
|
2756 |
|
|
'body' => array(
|
2757 |
|
|
'label' => t('Message body'),
|
2758 |
|
|
'description' => t('Message body'),
|
2759 |
|
|
'weight' => -4,
|
2760 |
|
|
),
|
2761 |
|
|
)
|
2762 |
|
|
);
|
2763 |
|
|
return $extra;
|
2764 |
|
|
}
|
2765 |
|
|
|
2766 |
|
|
/**
|
2767 |
|
|
* Implements hook_file_download_access().
|
2768 |
|
|
*/
|
2769 |
|
|
function privatemsg_file_download_access($field, $entity_type, $entity) {
|
2770 |
|
|
global $user;
|
2771 |
|
|
|
2772 |
|
|
if ($entity_type == 'privatemsg_message') {
|
2773 |
|
|
// Users with read all private messages permission can view all files too.
|
2774 |
|
|
if (user_access('read all private messages')) {
|
2775 |
|
|
return TRUE;
|
2776 |
|
|
}
|
2777 |
|
|
|
2778 |
|
|
// Check if user is a recipient of this message.
|
2779 |
|
|
return (bool)db_query_range("SELECT 1 FROM {pm_index} WHERE recipient = :uid AND type IN ('user', 'hidden') AND mid = :mid", 0, 1, array(':uid' => $user->uid, ':mid' => $entity->mid))->fetchField();
|
2780 |
|
|
}
|
2781 |
|
|
}
|
2782 |
|
|
|
2783 |
|
|
/**
|
2784 |
|
|
* Implements hook_token_info().
|
2785 |
|
|
*/
|
2786 |
|
|
function privatemsg_token_info() {
|
2787 |
|
|
$type = array(
|
2788 |
|
|
'name' => t('Private message'),
|
2789 |
|
|
'description' => t('Tokens related to private messages.'),
|
2790 |
|
|
'needs-data' => 'privatemsg_message',
|
2791 |
|
|
);
|
2792 |
|
|
|
2793 |
|
|
// Tokens for private messages.
|
2794 |
|
|
$message['mid'] = array(
|
2795 |
|
|
'name' => t("Message ID"),
|
2796 |
|
|
'description' => t("The unique ID of the message."),
|
2797 |
|
|
);
|
2798 |
|
|
$message['thread-id'] = array(
|
2799 |
|
|
'name' => t("Thread ID"),
|
2800 |
|
|
'description' => t("The unique ID of the thread."),
|
2801 |
|
|
);
|
2802 |
|
|
$message['url'] = array(
|
2803 |
|
|
'name' => t("URL"),
|
2804 |
|
|
'description' => t("URL that points to the message."),
|
2805 |
|
|
);
|
2806 |
|
|
$message['subject'] = array(
|
2807 |
|
|
'name' => t("Subject"),
|
2808 |
|
|
'description' => t("The subject of the message."),
|
2809 |
|
|
);
|
2810 |
|
|
$message['body'] = array(
|
2811 |
|
|
'name' => t("Body"),
|
2812 |
|
|
'description' => t("The body of the message."),
|
2813 |
|
|
);
|
2814 |
|
|
|
2815 |
|
|
// Chained tokens for nodes.
|
2816 |
|
|
$message['timestamp'] = array(
|
2817 |
|
|
'name' => t("Date created"),
|
2818 |
|
|
'description' => t("The date the message was sent."),
|
2819 |
|
|
'type' => 'date',
|
2820 |
|
|
);
|
2821 |
|
|
$message['author'] = array(
|
2822 |
|
|
'name' => t("Author"),
|
2823 |
|
|
'description' => t("The author of the message."),
|
2824 |
|
|
'type' => 'user',
|
2825 |
|
|
);
|
2826 |
|
|
$message['recipient'] = array(
|
2827 |
|
|
'name' => t("Recipient"),
|
2828 |
|
|
'description' => t("The recipient of the message."),
|
2829 |
|
|
'type' => 'user',
|
2830 |
|
|
);
|
2831 |
|
|
|
2832 |
|
|
return array(
|
2833 |
|
|
'types' => array('privatemsg_message' => $type),
|
2834 |
|
|
'tokens' => array('privatemsg_message' => $message),
|
2835 |
|
|
);
|
2836 |
|
|
}
|
2837 |
|
|
|
2838 |
|
|
/**
|
2839 |
|
|
* Implements hook_tokens().
|
2840 |
|
|
*/
|
2841 |
|
|
function privatemsg_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
2842 |
|
|
global $user;
|
2843 |
|
|
$url_options = array('absolute' => TRUE);
|
2844 |
|
|
if (isset($options['language'])) {
|
2845 |
|
|
$url_options['language'] = $options['language'];
|
2846 |
|
|
$language_code = $options['language']->language;
|
2847 |
|
|
}
|
2848 |
|
|
else {
|
2849 |
|
|
$language_code = NULL;
|
2850 |
|
|
}
|
2851 |
|
|
|
2852 |
|
|
$recipient = $user;
|
2853 |
|
|
if (isset($data['privatemsg_recipient'])) {
|
2854 |
|
|
$recipient = $data['privatemsg_recipient'];
|
2855 |
|
|
}
|
2856 |
|
|
|
2857 |
|
|
$sanitize = !empty($options['sanitize']);
|
2858 |
|
|
$replacements = array();
|
2859 |
|
|
if ($type == 'privatemsg_message' && !empty($data['privatemsg_message'])) {
|
2860 |
|
|
$message = $data['privatemsg_message'];
|
2861 |
|
|
|
2862 |
|
|
foreach ($tokens as $name => $original) {
|
2863 |
|
|
switch ($name) {
|
2864 |
|
|
case 'mid':
|
2865 |
|
|
$replacements[$original] = $message->mid;
|
2866 |
|
|
break;
|
2867 |
|
|
|
2868 |
|
|
case 'thread-id':
|
2869 |
|
|
$replacements[$original] = $message->thread_id;
|
2870 |
|
|
break;
|
2871 |
|
|
|
2872 |
|
|
case 'subject':
|
2873 |
|
|
// Avoid recursion.
|
2874 |
|
|
if (empty($options['privatemsg_recursion'])) {
|
2875 |
|
|
$subject = privatemsg_token_replace($message->subject, $data, $options + array('privatemsg_recursion' => 1));
|
2876 |
|
|
}
|
2877 |
|
|
else {
|
2878 |
|
|
$subject = $message->subject;
|
2879 |
|
|
}
|
2880 |
|
|
$replacements[$original] = $sanitize ? check_plain($subject) : $subject;
|
2881 |
|
|
break;
|
2882 |
|
|
|
2883 |
|
|
case 'body':
|
2884 |
|
|
// Avoid recursion.
|
2885 |
|
|
if (empty($options['privatemsg_recursion'])) {
|
2886 |
|
|
$replacements[$original] = privatemsg_token_replace($sanitize ? check_markup($message->body, $message->format) : $message->body, $data, $options + array('privatemsg_recursion' => 1));
|
2887 |
|
|
}
|
2888 |
|
|
else {
|
2889 |
|
|
$replacements[$original] = $sanitize ? check_markup($message->body, $message->format) : $message->body;
|
2890 |
|
|
}
|
2891 |
|
|
break;
|
2892 |
|
|
|
2893 |
|
|
case 'url':
|
2894 |
|
|
$uri = entity_uri('privatemsg_message', $message);
|
2895 |
|
|
$replacements[$original] = url($uri['path'], $url_options + $uri['options']);
|
2896 |
|
|
break;
|
2897 |
|
|
|
2898 |
|
|
// Default values for the chained tokens handled below.
|
2899 |
|
|
case 'author':
|
2900 |
|
|
$replacements[$original] = $sanitize ? filter_xss(privatemsg_recipient_format($message->author, array('plain' => TRUE))) : privatemsg_recipient_format($message->author, array('plain' => TRUE));
|
2901 |
|
|
break;
|
2902 |
|
|
|
2903 |
|
|
case 'recipient':
|
2904 |
|
|
$replacements[$original] = $sanitize ? filter_xss(privatemsg_recipient_format($recipient, array('plain' => TRUE))) : privatemsg_recipient_format($recipient, array('plain' => TRUE));
|
2905 |
|
|
break;
|
2906 |
|
|
|
2907 |
|
|
case 'timestamp':
|
2908 |
|
|
$replacements[$original] = format_date($message->timestamp, 'medium', '', NULL, $language_code);
|
2909 |
|
|
break;
|
2910 |
|
|
}
|
2911 |
|
|
}
|
2912 |
|
|
|
2913 |
|
|
if ($author_tokens = token_find_with_prefix($tokens, 'author')) {
|
2914 |
|
|
$replacements += token_generate('user', $author_tokens, array('user' => $message->author), $options);
|
2915 |
|
|
}
|
2916 |
|
|
|
2917 |
|
|
if ($recipient_tokens = token_find_with_prefix($tokens, 'recipient')) {
|
2918 |
|
|
$replacements += token_generate('user', $recipient_tokens, array('user' => $recipient), $options);
|
2919 |
|
|
}
|
2920 |
|
|
|
2921 |
|
|
if ($sent_tokens = token_find_with_prefix($tokens, 'timestamp')) {
|
2922 |
|
|
$replacements += token_generate('date', $sent_tokens, array('date' => $message->timestamp), $options);
|
2923 |
|
|
}
|
2924 |
|
|
}
|
2925 |
|
|
|
2926 |
|
|
return $replacements;
|
2927 |
|
|
}
|
2928 |
|
|
|
2929 |
|
|
/**
|
2930 |
|
|
* Wrapper function for token_replace() that does not replace the tokens if the
|
2931 |
|
|
* user viewing the message is not a recipient.
|
2932 |
|
|
*/
|
2933 |
|
|
function privatemsg_token_replace($text, $data, array $options = array()) {
|
2934 |
|
|
global $user;
|
2935 |
|
|
if (empty($data['privatemsg_recipient'])) {
|
2936 |
|
|
$recipient = $user;
|
2937 |
|
|
}
|
2938 |
|
|
else {
|
2939 |
|
|
$recipient = $data['privatemsg_recipient'];
|
2940 |
|
|
}
|
2941 |
|
|
|
2942 |
|
|
if (isset($options['language'])) {
|
2943 |
|
|
$url_options['language'] = $options['language'];
|
2944 |
|
|
$language_code = $options['language']->language;
|
2945 |
|
|
}
|
2946 |
|
|
else {
|
2947 |
|
|
$language_code = NULL;
|
2948 |
|
|
}
|
2949 |
|
|
|
2950 |
|
|
$message = $data['privatemsg_message'];
|
2951 |
|
|
$show_span = !isset($options['privatemsg-show-span']) || $options['privatemsg-show-span'];
|
2952 |
|
|
|
2953 |
|
|
// We do not replace tokens if the user viewing the message is the author or
|
2954 |
|
|
// not a real recipient to avoid confusion.
|
2955 |
|
|
$sql = "SELECT 1 FROM {pm_index} WHERE recipient = :uid AND type IN ('hidden', 'user') AND mid = :mid";
|
2956 |
|
|
$args = array(':uid' => $recipient->uid, ':mid' => $message->mid);
|
2957 |
|
|
if ($message->author->uid == $recipient->uid || !db_query($sql, $args)->fetchField()) {
|
2958 |
|
|
// Get all tokens of the message.
|
2959 |
|
|
$tokens = token_scan($text);
|
2960 |
|
|
$invalid_tokens = array();
|
2961 |
|
|
if (function_exists('token_get_invalid_tokens_by_context')) {
|
2962 |
|
|
$invalid_tokens = token_get_invalid_tokens_by_context($text, array('privatemsg_message'));
|
2963 |
|
|
}
|
2964 |
|
|
|
2965 |
|
|
if (!empty($tokens)) {
|
2966 |
|
|
$replacements = array();
|
2967 |
|
|
// Loop over the found tokens.
|
2968 |
|
|
foreach ($tokens as $tokens_type) {
|
2969 |
|
|
// token_replace() returns tokens separated by type.
|
2970 |
|
|
foreach ($tokens_type as $original) {
|
2971 |
|
|
// Displaying invalid tokens only works with token.module.
|
2972 |
|
|
if (in_array($original, $invalid_tokens)) {
|
2973 |
|
|
$token = t('INVALID TOKEN @token', array('@token' => $original), array('langcode' => $language_code));
|
2974 |
|
|
if (!$show_span) {
|
2975 |
|
|
$replacements[$original] = '< ' . $token . ' >';
|
2976 |
|
|
}
|
2977 |
|
|
else {
|
2978 |
|
|
$replacements[$original] = '<span class="privatemsg-token-invalid">< ' . $token . ' ></span>';
|
2979 |
|
|
}
|
2980 |
|
|
}
|
2981 |
|
|
else {
|
2982 |
|
|
$token = t('Token @token', array('@token' => $original), array('langcode' => $language_code));
|
2983 |
|
|
if (!$show_span) {
|
2984 |
|
|
$replacements[$original] = '< ' . $token . ' >';
|
2985 |
|
|
}
|
2986 |
|
|
else {
|
2987 |
|
|
$replacements[$original] = '<span class="privatemsg-token-valid">< ' . $token . ' ></span>';
|
2988 |
|
|
}
|
2989 |
|
|
}
|
2990 |
|
|
}
|
2991 |
|
|
}
|
2992 |
|
|
$text = str_replace(array_keys($replacements), $replacements, $text);
|
2993 |
|
|
|
2994 |
|
|
// If there are any tokens, add a notice that the tokens will be replaced
|
2995 |
|
|
// for the recipient.
|
2996 |
|
|
if (!empty($options['privatemsg-token-notice'])) {
|
2997 |
|
|
$text .= '<p class="privatemsg-token-notice">' . t('Note: Valid tokens will be replaced when a recipient is reading this message.') . '</p>';
|
2998 |
|
|
}
|
2999 |
|
|
}
|
3000 |
|
|
return $text;
|
3001 |
|
|
}
|
3002 |
|
|
|
3003 |
|
|
// If the user is a recipient, use default token_replace() function.
|
3004 |
|
|
return token_replace($text, $data, $options);
|
3005 |
|
|
}
|
3006 |
|
|
|
3007 |
|
|
/**
|
3008 |
|
|
* Implements hook_entity_property_info().
|
3009 |
|
|
*/
|
3010 |
|
|
function privatemsg_entity_property_info() {
|
3011 |
|
|
$info = array();
|
3012 |
|
|
// Add meta-data about the basic node properties.
|
3013 |
|
|
$properties = &$info['privatemsg_message']['properties'];
|
3014 |
|
|
$properties = array(
|
3015 |
|
|
'mid' => array(
|
3016 |
|
|
'type' => 'integer',
|
3017 |
|
|
'label' => t('Private message ID'),
|
3018 |
|
|
'description' => t('Private message ID'),
|
3019 |
|
|
),
|
3020 |
|
|
'thread_id' => array(
|
3021 |
|
|
'type' => 'integer',
|
3022 |
|
|
'label' => t('Private message thread ID'),
|
3023 |
|
|
'description' => t('Private message thread ID'),
|
3024 |
|
|
'getter callback' => 'entity_property_verbatim_get',
|
3025 |
|
|
),
|
3026 |
|
|
'author' => array(
|
3027 |
|
|
'type' => 'user',
|
3028 |
|
|
'label' => t('Private message author'),
|
3029 |
|
|
'description' => t('Private message author'),
|
3030 |
|
|
'setter callback' => 'entity_property_verbatim_set',
|
3031 |
|
|
),
|
3032 |
|
|
'subject' => array(
|
3033 |
|
|
'type' => 'text',
|
3034 |
|
|
'label' => t('Private message subject'),
|
3035 |
|
|
'description' => t('Private message subject'),
|
3036 |
|
|
'setter callback' => 'entity_property_verbatim_set',
|
3037 |
|
|
),
|
3038 |
|
|
'body' => array(
|
3039 |
|
|
'type' => 'text',
|
3040 |
|
|
'label' => t('Private message body'),
|
3041 |
|
|
'description' => t('Private message body'),
|
3042 |
|
|
'setter callback' => 'entity_property_verbatim_set',
|
3043 |
|
|
),
|
3044 |
|
|
'timestamp' => array(
|
3045 |
|
|
'type' => 'date',
|
3046 |
|
|
'label' => t('Private message sent date'),
|
3047 |
|
|
'description' => t('Private message sent date'),
|
3048 |
|
|
),
|
3049 |
|
|
);
|
3050 |
|
|
return $info;
|
3051 |
|
|
}
|
3052 |
|
|
|
3053 |
|
|
/**
|
3054 |
|
|
* Private message controller, loads private messages.
|
3055 |
|
|
*/
|
3056 |
|
|
class PrivatemsgMessageController extends DrupalDefaultEntityController {
|
3057 |
|
|
|
3058 |
|
|
protected $account = NULL;
|
3059 |
|
|
|
3060 |
|
|
protected function attachLoad(&$messages, $revision_id = FALSE) {
|
3061 |
|
|
global $user;
|
3062 |
|
|
foreach ($messages as $message) {
|
3063 |
|
|
$message->user = $this->account ? $this->account : $user;
|
3064 |
|
|
// Load author of message.
|
3065 |
|
|
if (!($message->author = user_load($message->author))) {
|
3066 |
|
|
// If user does not exist, load anonymous user.
|
3067 |
|
|
$message->author = user_load(0);
|
3068 |
|
|
}
|
3069 |
|
|
}
|
3070 |
|
|
parent::attachLoad($messages, $revision_id);
|
3071 |
|
|
}
|
3072 |
|
|
|
3073 |
|
|
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
|
3074 |
|
|
// Remove account from conditions.
|
3075 |
|
|
if (isset($conditions['account'])) {
|
3076 |
|
|
$this->account = $conditions['account'];
|
3077 |
|
|
unset($conditions['account']);
|
3078 |
|
|
}
|
3079 |
|
|
|
3080 |
|
|
$query = parent::buildQuery($ids, $conditions, $revision_id);
|
3081 |
|
|
$query->fields('pmi', array('is_new', 'thread_id'));
|
3082 |
|
|
if ($this->account) {
|
3083 |
|
|
$query
|
3084 |
|
|
->condition('pmi.recipient', $this->account->uid)
|
3085 |
|
|
->condition('pmi.type', array('hidden', 'user'));
|
3086 |
|
|
}
|
3087 |
|
|
else {
|
3088 |
|
|
// If no account is given, at least limit the result to a single row per
|
3089 |
|
|
// message.
|
3090 |
|
|
$query->distinct();
|
3091 |
|
|
}
|
3092 |
|
|
$query->join('pm_index', 'pmi', "base.mid = pmi.mid");
|
3093 |
|
|
return $query;
|
3094 |
|
|
}
|
3095 |
|
|
}
|
3096 |
|
|
|
3097 |
|
|
/**
|
3098 |
|
|
* Implements hook_date_formats().
|
3099 |
|
|
*/
|
3100 |
|
|
function privatemsg_date_formats() {
|
3101 |
|
|
$formats = array('g:i a', 'H:i', 'M j', 'j M', 'm/d/y', 'd/m/y', 'j/n/y', 'n/j/y');
|
3102 |
|
|
$types = array_keys(privatemsg_date_format_types());
|
3103 |
|
|
$date_formats = array();
|
3104 |
|
|
foreach ($types as $type) {
|
3105 |
|
|
foreach ($formats as $format) {
|
3106 |
|
|
$date_formats[] = array(
|
3107 |
|
|
'type' => $type,
|
3108 |
|
|
'format' => $format,
|
3109 |
|
|
'locales' => array(),
|
3110 |
|
|
);
|
3111 |
|
|
}
|
3112 |
|
|
}
|
3113 |
|
|
return $date_formats;
|
3114 |
|
|
}
|
3115 |
|
|
|
3116 |
|
|
/**
|
3117 |
|
|
* Implements hook_date_format_types().
|
3118 |
|
|
*/
|
3119 |
|
|
function privatemsg_date_format_types() {
|
3120 |
|
|
return array(
|
3121 |
|
|
'privatemsg_current_day' => t('Privatemsg: Current day'),
|
3122 |
|
|
'privatemsg_current_year' => t('Privatemsg: Current year'),
|
3123 |
|
|
'privatemsg_years' => t('Privatemsg: Other years'),
|
3124 |
|
|
);
|
3125 |
|
|
}
|
3126 |
|
|
|
3127 |
|
|
/**
|
3128 |
|
|
* Formats a timestamp according to the defines rules.
|
3129 |
|
|
*
|
3130 |
|
|
* Examples/Rules:
|
3131 |
|
|
*
|
3132 |
|
|
* Current hour: 25 min ago
|
3133 |
|
|
* Current day (but not within the hour): 10:30 am
|
3134 |
|
|
* Current year (but not on the same day): Nov 25
|
3135 |
|
|
* Prior years (not the current year): 11/25/08
|
3136 |
|
|
*
|
3137 |
|
|
* @param $timestamp
|
3138 |
|
|
* UNIX Timestamp.
|
3139 |
|
|
*
|
3140 |
|
|
* @return
|
3141 |
|
|
* The formatted date.
|
3142 |
|
|
*/
|
3143 |
|
|
function privatemsg_format_date($timestamp) {
|
3144 |
|
|
if ($timestamp > ((int)(REQUEST_TIME / 3600)) * 3600) {
|
3145 |
|
|
return t('@interval ago', array('@interval' => format_interval(abs(REQUEST_TIME - $timestamp), 1)));
|
3146 |
|
|
}
|
3147 |
|
|
if ($timestamp > ((int)(REQUEST_TIME / 86400)) * 86400) {
|
3148 |
|
|
return format_date($timestamp, 'privatemsg_current_day');
|
3149 |
|
|
}
|
3150 |
|
|
if ($timestamp > mktime(0, 0, 0, 1, 0, date('Y'))) {
|
3151 |
|
|
return format_date($timestamp, 'privatemsg_current_year');
|
3152 |
|
|
}
|
3153 |
|
|
return format_date($timestamp, 'privatemsg_years');
|
3154 |
|
|
} |