1<?php
2// +-----------------------------------------------------------------------+
3// | This file is part of Piwigo.                                          |
4// |                                                                       |
5// | For copyright and license information, please view the COPYING.txt    |
6// | file that was distributed with this source code.                      |
7// +-----------------------------------------------------------------------+
8
9/**
10 * @package functions\comment
11 */
12
13
14add_event_handler('user_comment_check', 'user_comment_check');
15
16/**
17 * Does basic check on comment and returns action to perform.
18 * This method is called by a trigger_change()
19 *
20 * @param string $action before check
21 * @param array $comment
22 * @return string validate, moderate, reject
23 */
24function user_comment_check($action, $comment)
25{
26  global $conf,$user;
27
28  if ($action=='reject')
29    return $action;
30
31  $my_action = $conf['comment_spam_reject'] ? 'reject':'moderate';
32
33  if ($action==$my_action)
34    return $action;
35
36  // we do here only BASIC spam check (plugins can do more)
37  if ( !is_a_guest() )
38    return $action;
39
40  $link_count = preg_match_all( '/https?:\/\//',
41    $comment['content'], $matches);
42
43  if ( strpos($comment['author'], 'http://')!==false )
44  {
45    $link_count++;
46  }
47
48  if ( $link_count>$conf['comment_spam_max_links'] )
49  {
50    $_POST['cr'][] = 'links';
51    return $my_action;
52  }
53  return $action;
54}
55
56/**
57 * Tries to insert a user comment and returns action to perform.
58 *
59 * @param array &$comm
60 * @param string $key secret key sent back to the browser
61 * @param array &$infos output array of error messages
62 * @return string validate, moderate, reject
63 */
64function insert_user_comment(&$comm, $key, &$infos)
65{
66  global $conf, $user;
67
68  $comm = array_merge( $comm,
69    array(
70      'ip' => $_SERVER['REMOTE_ADDR'],
71      'agent' => $_SERVER['HTTP_USER_AGENT']
72    )
73   );
74
75  $infos = array();
76  if (!$conf['comments_validation'] or is_admin())
77  {
78    $comment_action='validate'; //one of validate, moderate, reject
79  }
80  else
81  {
82    $comment_action='moderate'; //one of validate, moderate, reject
83  }
84
85  // display author field if the user status is guest or generic
86  if (!is_classic_user())
87  {
88    if ( empty($comm['author']) )
89    {
90      if ($conf['comments_author_mandatory'])
91      {
92        $infos[] = l10n('Username is mandatory');
93        $comment_action='reject';
94      }
95      $comm['author'] = 'guest';
96    }
97    $comm['author_id'] = $conf['guest_id'];
98    // if a guest try to use the name of an already existing user, he must be
99    // rejected
100    if ( $comm['author'] != 'guest' )
101    {
102      $query = '
103SELECT COUNT(*) AS user_exists
104  FROM '.USERS_TABLE.'
105  WHERE '.$conf['user_fields']['username']." = '".addslashes($comm['author'])."'";
106      $row = pwg_db_fetch_assoc( pwg_query( $query ) );
107      if ( $row['user_exists'] == 1 )
108      {
109        $infos[] = l10n('This login is already used by another user');
110        $comment_action='reject';
111      }
112    }
113  }
114  else
115  {
116    $comm['author'] = addslashes($user['username']);
117    $comm['author_id'] = $user['id'];
118  }
119
120  if ( empty($comm['content']) )
121  { // empty comment content
122    $comment_action='reject';
123  }
124
125  if ( !verify_ephemeral_key(@$key, $comm['image_id']) )
126  {
127    $comment_action='reject';
128    $_POST['cr'][] = 'key'; // rvelices: I use this outside to see how spam robots work
129  }
130
131  // website
132  if (!empty($comm['website_url']))
133  {
134    if (!$conf['comments_enable_website'])
135    { // honeypot: if the field is disabled, it should be empty !
136      $comment_action='reject';
137      $_POST['cr'][] = 'website_url';
138    }
139    else
140    {
141      $comm['website_url'] = strip_tags($comm['website_url']);
142      if (!preg_match('/^https?/i', $comm['website_url']))
143      {
144        $comm['website_url'] = 'http://'.$comm['website_url'];
145      }
146      if (!url_check_format($comm['website_url']))
147      {
148        $infos[] = l10n('Your website URL is invalid');
149        $comment_action='reject';
150      }
151    }
152  }
153
154  // email
155  if (empty($comm['email']))
156  {
157    if (!empty($user['email']))
158    {
159      $comm['email'] = $user['email'];
160    }
161    elseif ($conf['comments_email_mandatory'])
162    {
163      $infos[] = l10n('Email address is missing. Please specify an email address.');
164      $comment_action='reject';
165    }
166  }
167  elseif (!email_check_format($comm['email']))
168  {
169    $infos[] = l10n('mail address must be like xxx@yyy.eee (example : jack@altern.org)');
170    $comment_action='reject';
171  }
172
173  // anonymous id = ip address
174  $ip_components = explode('.', $comm['ip']);
175  if (count($ip_components) > 3)
176  {
177    array_pop($ip_components);
178  }
179  $anonymous_id = implode('.', $ip_components);
180
181  if ($comment_action!='reject' and $conf['anti-flood_time']>0 and !is_admin())
182  { // anti-flood system
183    $reference_date = pwg_db_get_flood_period_expression($conf['anti-flood_time']);
184
185    $query = '
186SELECT count(1) FROM '.COMMENTS_TABLE.'
187  WHERE date > '.$reference_date.'
188    AND author_id = '.$comm['author_id'];
189    if (!is_classic_user())
190    {
191      $query.= '
192      AND anonymous_id LIKE "'.$anonymous_id.'.%"';
193    }
194    $query.= '
195;';
196
197    list($counter) = pwg_db_fetch_row(pwg_query($query));
198    if ( $counter > 0 )
199    {
200      $infos[] = l10n('Anti-flood system : please wait for a moment before trying to post another comment');
201      $comment_action='reject';
202      $_POST['cr'][] = 'flood_time';
203    }
204  }
205
206  // perform more spam check
207  $comment_action = trigger_change('user_comment_check',
208      $comment_action, $comm
209    );
210
211  if ( $comment_action!='reject' )
212  {
213    $query = '
214INSERT INTO '.COMMENTS_TABLE.'
215  (author, author_id, anonymous_id, content, date, validated, validation_date, image_id, website_url, email)
216  VALUES (
217    \''.$comm['author'].'\',
218    '.$comm['author_id'].',
219    \''.$comm['ip'].'\',
220    \''.$comm['content'].'\',
221    NOW(),
222    \''.($comment_action=='validate' ? 'true':'false').'\',
223    '.($comment_action=='validate' ? 'NOW()':'NULL').',
224    '.$comm['image_id'].',
225    '.(!empty($comm['website_url']) ? '\''.$comm['website_url'].'\'' : 'NULL').',
226    '.(!empty($comm['email']) ? '\''.$comm['email'].'\'' : 'NULL').'
227  )
228';
229    pwg_query($query);
230    $comm['id'] = pwg_db_insert_id(COMMENTS_TABLE);
231
232    invalidate_user_cache_nb_comments();
233
234    if ( ($conf['email_admin_on_comment'] && 'validate' == $comment_action)
235        or ($conf['email_admin_on_comment_validation'] and 'moderate' == $comment_action))
236    {
237      include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
238
239      $comment_url = get_absolute_root_url().'comments.php?comment_id='.$comm['id'];
240
241      $keyargs_content = array(
242        get_l10n_args('Author: %s', stripslashes($comm['author']) ),
243        get_l10n_args('Email: %s', stripslashes($comm['email']) ),
244        get_l10n_args('Comment: %s', stripslashes($comm['content']) ),
245        get_l10n_args(''),
246        get_l10n_args('Manage this user comment: %s', $comment_url),
247      );
248
249      if ('moderate' == $comment_action)
250      {
251        $keyargs_content[] = get_l10n_args('(!) This comment requires validation');
252      }
253
254      pwg_mail_notification_admins(
255        get_l10n_args('Comment by %s', stripslashes($comm['author']) ),
256        $keyargs_content
257      );
258    }
259  }
260
261  return $comment_action;
262}
263
264/**
265 * Tries to delete a (or more) user comment.
266 *    only admin can delete all comments
267 *    other users can delete their own comments
268 *
269 * @param int|int[] $comment_id
270 * @return bool false if nothing deleted
271 */
272function delete_user_comment($comment_id)
273{
274  $user_where_clause = '';
275  if (!is_admin())
276  {
277    $user_where_clause = '   AND author_id = \''.$GLOBALS['user']['id'].'\'';
278  }
279
280  if (is_array($comment_id))
281    $where_clause = 'id IN('.implode(',', $comment_id).')';
282  else
283    $where_clause = 'id = '.$comment_id;
284
285  $query = '
286DELETE FROM '.COMMENTS_TABLE.'
287  WHERE '.$where_clause.
288$user_where_clause.'
289;';
290
291  if ( pwg_db_changes(pwg_query($query)) )
292  {
293    invalidate_user_cache_nb_comments();
294
295    email_admin('delete',
296                array('author' => $GLOBALS['user']['username'],
297                      'comment_id' => $comment_id
298                  ));
299    trigger_notify('user_comment_deletion', $comment_id);
300
301    return true;
302  }
303
304  return false;
305}
306
307/**
308 * Tries to update a user comment
309 *    only admin can update all comments
310 *    users can edit their own comments if admin allow them
311 *
312 * @param array $comment
313 * @param string $post_key secret key sent back to the browser
314 * @return string validate, moderate, reject
315 */
316
317function update_user_comment($comment, $post_key)
318{
319  global $conf, $page;
320
321  $comment_action = 'validate';
322
323  if ( !verify_ephemeral_key($post_key, $comment['image_id']) )
324  {
325    $comment_action='reject';
326  }
327  elseif (!$conf['comments_validation'] or is_admin()) // should the updated comment must be validated
328  {
329    $comment_action='validate'; //one of validate, moderate, reject
330  }
331  else
332  {
333    $comment_action='moderate'; //one of validate, moderate, reject
334  }
335
336  // perform more spam check
337  $comment_action =
338    trigger_change('user_comment_check',
339		  $comment_action,
340		  array_merge($comment,
341			      array('author' => $GLOBALS['user']['username'])
342			      )
343		  );
344
345  // website
346  if (!empty($comment['website_url']))
347  {
348    $comm['website_url'] = strip_tags($comm['website_url']);
349    if (!preg_match('/^https?/i', $comment['website_url']))
350    {
351      $comment['website_url'] = 'http://'.$comment['website_url'];
352    }
353    if (!url_check_format($comment['website_url']))
354    {
355      $page['errors'][] = l10n('Your website URL is invalid');
356      $comment_action='reject';
357    }
358  }
359
360  if ( $comment_action!='reject' )
361  {
362    $user_where_clause = '';
363    if (!is_admin())
364    {
365      $user_where_clause = '   AND author_id = \''.
366	$GLOBALS['user']['id'].'\'';
367    }
368
369    $query = '
370UPDATE '.COMMENTS_TABLE.'
371  SET content = \''.$comment['content'].'\',
372      website_url = '.(!empty($comment['website_url']) ? '\''.$comment['website_url'].'\'' : 'NULL').',
373      validated = \''.($comment_action=='validate' ? 'true':'false').'\',
374      validation_date = '.($comment_action=='validate' ? 'NOW()':'NULL').'
375  WHERE id = '.$comment['comment_id'].
376$user_where_clause.'
377;';
378    $result = pwg_query($query);
379
380    // mail admin and ask to validate the comment
381    if ($result and $conf['email_admin_on_comment_validation'] and 'moderate' == $comment_action)
382    {
383      include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
384
385      $comment_url = get_absolute_root_url().'comments.php?comment_id='.$comment['comment_id'];
386
387      $keyargs_content = array(
388        get_l10n_args('Author: %s', stripslashes($GLOBALS['user']['username']) ),
389        get_l10n_args('Comment: %s', stripslashes($comment['content']) ),
390        get_l10n_args(''),
391        get_l10n_args('Manage this user comment: %s', $comment_url),
392        get_l10n_args('(!) This comment requires validation'),
393      );
394
395      pwg_mail_notification_admins(
396        get_l10n_args('Comment by %s', stripslashes($GLOBALS['user']['username']) ),
397        $keyargs_content
398      );
399    }
400    // just mail admin
401    elseif ($result)
402    {
403      email_admin('edit', array('author' => $GLOBALS['user']['username'],
404				'content' => stripslashes($comment['content'])) );
405    }
406  }
407
408  return $comment_action;
409}
410
411/**
412 * Notifies admins about updated or deleted comment.
413 * Only used when no validation is needed, otherwise pwg_mail_notification_admins() is used.
414 *
415 * @param string $action edit, delete
416 * @param array $comment
417 */
418function email_admin($action, $comment)
419{
420  global $conf;
421
422  if (!in_array($action, array('edit', 'delete'))
423      or (($action=='edit') and !$conf['email_admin_on_comment_edition'])
424      or (($action=='delete') and !$conf['email_admin_on_comment_deletion']))
425  {
426    return;
427  }
428
429  include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
430
431  $keyargs_content = array(
432    get_l10n_args('Author: %s', $comment['author']),
433    );
434
435  if ($action=='delete')
436  {
437    $keyargs_content[] = get_l10n_args('This author removed the comment with id %d', $comment['comment_id']);
438  }
439  else
440  {
441    $keyargs_content[] = get_l10n_args('This author modified following comment:');
442    $keyargs_content[] = get_l10n_args('Comment: %s', $comment['content']);
443  }
444
445  pwg_mail_notification_admins(
446    get_l10n_args('Comment by %s', $comment['author']),
447    $keyargs_content
448    );
449}
450
451/**
452 * Returns the author id of a comment
453 *
454 * @param int $comment_id
455 * @param bool $die_on_error
456 * @return int
457 */
458function get_comment_author_id($comment_id, $die_on_error=true)
459{
460  $query = '
461SELECT
462    author_id
463  FROM '.COMMENTS_TABLE.'
464  WHERE id = '.$comment_id.'
465;';
466  $result = pwg_query($query);
467  if (pwg_db_num_rows($result) == 0)
468  {
469    if ($die_on_error)
470    {
471      fatal_error('Unknown comment identifier');
472    }
473    else
474    {
475      return false;
476    }
477  }
478
479  list($author_id) = pwg_db_fetch_row($result);
480
481  return $author_id;
482}
483
484/**
485 * Tries to validate a user comment.
486 *
487 * @param int|int[] $comment_id
488 */
489function validate_user_comment($comment_id)
490{
491  if (is_array($comment_id))
492    $where_clause = 'id IN('.implode(',', $comment_id).')';
493  else
494    $where_clause = 'id = '.$comment_id;
495
496  $query = '
497UPDATE '.COMMENTS_TABLE.'
498  SET validated = \'true\'
499    , validation_date = NOW()
500  WHERE '.$where_clause.'
501;';
502  pwg_query($query);
503
504  invalidate_user_cache_nb_comments();
505  trigger_notify('user_comment_validation', $comment_id);
506}
507
508/**
509 * Clears cache of nb comments for all users
510 */
511function invalidate_user_cache_nb_comments()
512{
513  global $user;
514
515  unset($user['nb_available_comments']);
516
517  $query = '
518UPDATE '.USER_CACHE_TABLE.'
519  SET nb_available_comments = NULL
520;';
521  pwg_query($query);
522}
523
524?>