source: trunk/include/functions_user.inc.php @ 29111

Last change on this file since 29111 was 29111, checked in by plg, 10 years ago

bug 3050: increase security on reset password algorithm.

  • reset key has a 1-hour life
  • reset key is automatically deleted once used
  • reset key is stored as a hash

Thank you effigies for code suggestions

  • Property svn:eol-style set to LF
File size: 36.1 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2014 Piwigo Team                  http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24/**
25 * @package functions\user
26 */
27
28
29/**
30 * Checks if an email is well formed and not already in use.
31 *
32 * @param int $user_id
33 * @param string $mail_address
34 * @return string|void error message or nothing
35 */
36function validate_mail_address($user_id, $mail_address)
37{
38  global $conf;
39
40  if (empty($mail_address) and
41      !($conf['obligatory_user_mail_address'] and
42      in_array(script_basename(), array('register', 'profile'))))
43  {
44    return '';
45  }
46
47  if ( !email_check_format($mail_address) )
48  {
49    return l10n('mail address must be like xxx@yyy.eee (example : jack@altern.org)');
50  }
51
52  if (defined("PHPWG_INSTALLED") and !empty($mail_address))
53  {
54    $query = '
55SELECT count(*)
56FROM '.USERS_TABLE.'
57WHERE upper('.$conf['user_fields']['email'].') = upper(\''.$mail_address.'\')
58'.(is_numeric($user_id) ? 'AND '.$conf['user_fields']['id'].' != \''.$user_id.'\'' : '').'
59;';
60    list($count) = pwg_db_fetch_row(pwg_query($query));
61    if ($count != 0)
62    {
63      return l10n('this email address is already in use');
64    }
65  }
66}
67
68/**
69 * Checks if a login is not already in use.
70 * Comparision is case insensitive.
71 *
72 * @param string $login
73 * @return string|void error message or nothing
74 */
75function validate_login_case($login)
76{
77  global $conf;
78
79  if (defined("PHPWG_INSTALLED"))
80  {
81    $query = "
82SELECT ".$conf['user_fields']['username']."
83FROM ".USERS_TABLE."
84WHERE LOWER(".stripslashes($conf['user_fields']['username']).") = '".strtolower($login)."'
85;";
86
87    $count = pwg_db_num_rows(pwg_query($query));
88
89    if ($count > 0)
90    {
91      return l10n('this login is already used');
92    }
93  }
94}
95/**
96 * Searches for user with the same username in different case.
97 *
98 * @param string $username typically typed in by user for identification
99 * @return string $username found in database
100 */
101function search_case_username($username)
102{
103  global $conf;
104
105  $username_lo = strtolower($username);
106
107  $SCU_users = array();
108
109  $q = pwg_query("
110    SELECT ".$conf['user_fields']['username']." AS username
111    FROM `".USERS_TABLE."`;
112  ");
113  while ($r = pwg_db_fetch_assoc($q))
114   $SCU_users[$r['username']] = strtolower($r['username']);
115   // $SCU_users is now an associative table where the key is the account as
116   // registered in the DB, and the value is this same account, in lower case
117
118  $users_found = array_keys($SCU_users, $username_lo);
119  // $users_found is now a table of which the values are all the accounts
120  // which can be written in lowercase the same way as $username
121  if (count($users_found) != 1) // If ambiguous, don't allow lowercase writing
122   return $username; // but normal writing will work
123  else
124   return $users_found[0];
125}
126
127/**
128 * Creates a new user.
129 *
130 * @param string $login
131 * @param string $password
132 * @param string $mail_adress
133 * @param bool $notify_admin
134 * @param array &$errors populated with error messages
135 * @param bool $notify_user
136 * @return int|false user id or false
137 */
138function register_user($login, $password, $mail_address, $notify_admin=true, &$errors = array(), $notify_user=false)
139{
140  global $conf;
141
142  if ($login == '')
143  {
144    $errors[] = l10n('Please, enter a login');
145  }
146  if (preg_match('/^.* $/', $login))
147  {
148    $errors[] = l10n('login mustn\'t end with a space character');
149  }
150  if (preg_match('/^ .*$/', $login))
151  {
152    $errors[] = l10n('login mustn\'t start with a space character');
153  }
154  if (get_userid($login))
155  {
156    $errors[] = l10n('this login is already used');
157  }
158  if ($login != strip_tags($login))
159  {
160    $errors[] = l10n('html tags are not allowed in login');
161  }
162  $mail_error = validate_mail_address(null, $mail_address);
163  if ('' != $mail_error)
164  {
165    $errors[] = $mail_error;
166  }
167
168  if ($conf['insensitive_case_logon'] == true)
169  {
170    $login_error = validate_login_case($login);
171    if ($login_error != '')
172    {
173      $errors[] = $login_error;
174    }
175  }
176
177  $errors = trigger_change(
178    'register_user_check',
179    $errors,
180    array(
181      'username'=>$login,
182      'password'=>$password,
183      'email'=>$mail_address,
184      )
185    );
186
187  // if no error until here, registration of the user
188  if (count($errors) == 0)
189  {
190    $insert = array(
191      $conf['user_fields']['username'] => pwg_db_real_escape_string($login),
192      $conf['user_fields']['password'] => $conf['password_hash']($password),
193      $conf['user_fields']['email'] => $mail_address
194      );
195
196    single_insert(USERS_TABLE, $insert);
197    $user_id = pwg_db_insert_id();
198
199    // Assign by default groups
200    $query = '
201SELECT id
202  FROM '.GROUPS_TABLE.'
203  WHERE is_default = \''.boolean_to_string(true).'\'
204  ORDER BY id ASC
205;';
206    $result = pwg_query($query);
207
208    $inserts = array();
209    while ($row = pwg_db_fetch_assoc($result))
210    {
211      $inserts[] = array(
212        'user_id' => $user_id,
213        'group_id' => $row['id']
214        );
215    }
216
217    if (count($inserts) != 0)
218    {
219      mass_inserts(USER_GROUP_TABLE, array('user_id', 'group_id'), $inserts);
220    }
221
222    $override = null;
223    if ($notify_admin and $conf['browser_language'])
224    {
225      if (!get_browser_language($override['language']))
226      {
227        $override=null;
228      }
229    }
230    create_user_infos($user_id, $override);
231
232    if ($notify_admin and $conf['email_admin_on_new_user'])
233    {
234      include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
235      $admin_url = get_absolute_root_url().'admin.php?page=user_list&username='.$login;
236
237      $keyargs_content = array(
238        get_l10n_args('User: %s', stripslashes($login) ),
239        get_l10n_args('Email: %s', $_POST['mail_address']),
240        get_l10n_args(''),
241        get_l10n_args('Admin: %s', $admin_url),
242        );
243
244      pwg_mail_notification_admins(
245        get_l10n_args('Registration of %s', stripslashes($login) ),
246        $keyargs_content
247        );
248    }
249
250    if ($notify_user and email_check_format($mail_address))
251    {
252      include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
253
254      $keyargs_content = array(
255        get_l10n_args('Hello %s,', stripslashes($login)),
256        get_l10n_args('Thank you for registering at %s!', $conf['gallery_title']),
257        get_l10n_args('', ''),
258        get_l10n_args('Here are your connection settings', ''),
259        get_l10n_args('', ''),
260        get_l10n_args('Link: %s', get_absolute_root_url()),
261        get_l10n_args('Username: %s', stripslashes($login)),
262        get_l10n_args('Password: %s', stripslashes($password)),
263        get_l10n_args('Email: %s', $mail_address),
264        get_l10n_args('', ''),
265        get_l10n_args('If you think you\'ve received this email in error, please contact us at %s', get_webmaster_mail_address()),
266        );
267
268      pwg_mail(
269        $mail_address,
270        array(
271          'subject' => '['.$conf['gallery_title'].'] '.l10n('Registration'),
272          'content' => l10n_args($keyargs_content),
273          'content_format' => 'text/plain',
274          )
275        );
276    }
277
278    trigger_notify(
279      'register_user',
280      array(
281        'id'=>$user_id,
282        'username'=>$login,
283        'email'=>$mail_address,
284        )
285      );
286
287    return $user_id;
288  }
289  else
290  {
291    return false;
292  }
293}
294
295/**
296 * Fetches user data from database.
297 * Same that getuserdata() but with additional tests for guest.
298 *
299 * @param int $user_id
300 * @param boolean $user_cache
301 * @return array
302 */
303function build_user($user_id, $use_cache=true)
304{
305  global $conf;
306
307  $user['id'] = $user_id;
308  $user = array_merge( $user, getuserdata($user_id, $use_cache) );
309
310  if ($user['id'] == $conf['guest_id'] and $user['status'] <> 'guest')
311  {
312    $user['status'] = 'guest';
313    $user['internal_status']['guest_must_be_guest'] = true;
314  }
315
316  // Check user theme
317  if (!isset($user['theme_name']))
318  {
319    $user['theme'] = get_default_theme();
320  }
321
322  return $user;
323}
324
325/**
326 * Finds informations related to the user identifier.
327 *
328 * @param int $user_id
329 * @param boolean $use_cache
330 * @return array
331 */
332function getuserdata($user_id, $use_cache=false)
333{
334  global $conf;
335
336  // retrieve basic user data
337  $query = '
338SELECT ';
339  $is_first = true;
340  foreach ($conf['user_fields'] as $pwgfield => $dbfield)
341  {
342    if ($is_first)
343    {
344      $is_first = false;
345    }
346    else
347    {
348      $query.= '
349     , ';
350    }
351    $query.= $dbfield.' AS '.$pwgfield;
352  }
353  $query.= '
354  FROM '.USERS_TABLE.'
355  WHERE '.$conf['user_fields']['id'].' = \''.$user_id.'\'';
356
357  $row = pwg_db_fetch_assoc(pwg_query($query));
358
359  // retrieve additional user data ?
360  if ($conf['external_authentification'])
361  {
362    $query = '
363SELECT
364    COUNT(1) AS counter
365  FROM '.USER_INFOS_TABLE.' AS ui
366    LEFT JOIN '.USER_CACHE_TABLE.' AS uc ON ui.user_id = uc.user_id
367    LEFT JOIN '.THEMES_TABLE.' AS t ON t.id = ui.theme
368  WHERE ui.user_id = '.$user_id.'
369  GROUP BY ui.user_id
370;';
371    list($counter) = pwg_db_fetch_row(pwg_query($query));
372    if ($counter != 1)
373    {
374      create_user_infos($user_id);
375    }
376  }
377
378  // retrieve user info
379  $query = '
380SELECT
381    ui.*,
382    uc.*,
383    t.name AS theme_name
384  FROM '.USER_INFOS_TABLE.' AS ui
385    LEFT JOIN '.USER_CACHE_TABLE.' AS uc ON ui.user_id = uc.user_id
386    LEFT JOIN '.THEMES_TABLE.' AS t ON t.id = ui.theme
387  WHERE ui.user_id = '.$user_id.'
388;';
389
390  $result = pwg_query($query);
391  $user_infos_row = pwg_db_fetch_assoc($result);
392
393  // then merge basic + additional user data
394  $userdata = array_merge($row, $user_infos_row);
395
396  foreach ($userdata as &$value)
397  {
398      // If the field is true or false, the variable is transformed into a boolean value.
399      if ($value == 'true')
400      {
401        $value = true;
402      }
403      elseif ($value == 'false')
404      {
405        $value = false;
406      }
407  }
408  unset($value);
409
410  if ($use_cache)
411  {
412    if (!isset($userdata['need_update'])
413        or !is_bool($userdata['need_update'])
414        or $userdata['need_update'] == true)
415    {
416      $userdata['cache_update_time'] = time();
417
418      // Set need update are done
419      $userdata['need_update'] = false;
420
421      $userdata['forbidden_categories'] =
422        calculate_permissions($userdata['id'], $userdata['status']);
423
424      /* now we build the list of forbidden images (this list does not contain
425      images that are not in at least an authorized category)*/
426      $query = '
427SELECT DISTINCT(id)
428  FROM '.IMAGES_TABLE.' INNER JOIN '.IMAGE_CATEGORY_TABLE.' ON id=image_id
429  WHERE category_id NOT IN ('.$userdata['forbidden_categories'].')
430    AND level>'.$userdata['level'];
431      $forbidden_ids = query2array($query,null, 'id');
432
433      if ( empty($forbidden_ids) )
434      {
435        $forbidden_ids[] = 0;
436      }
437      $userdata['image_access_type'] = 'NOT IN'; //TODO maybe later
438      $userdata['image_access_list'] = implode(',',$forbidden_ids);
439
440
441      $query = '
442SELECT COUNT(DISTINCT(image_id)) as total
443  FROM '.IMAGE_CATEGORY_TABLE.'
444  WHERE category_id NOT IN ('.$userdata['forbidden_categories'].')
445    AND image_id '.$userdata['image_access_type'].' ('.$userdata['image_access_list'].')';
446      list($userdata['nb_total_images']) = pwg_db_fetch_row(pwg_query($query));
447
448
449      // now we update user cache categories
450      $user_cache_cats = get_computed_categories($userdata, null);
451      if ( !is_admin($userdata['status']) )
452      { // for non admins we forbid categories with no image (feature 1053)
453        $forbidden_ids = array();
454        foreach ($user_cache_cats as $cat)
455        {
456          if ($cat['count_images']==0)
457          {
458            $forbidden_ids[] = $cat['cat_id'];
459            remove_computed_category($user_cache_cats, $cat);
460          }
461        }
462        if ( !empty($forbidden_ids) )
463        {
464          if ( empty($userdata['forbidden_categories']) )
465          {
466            $userdata['forbidden_categories'] = implode(',', $forbidden_ids);
467          }
468          else
469          {
470            $userdata['forbidden_categories'] .= ','.implode(',', $forbidden_ids);
471          }
472        }
473      }
474
475      // delete user cache
476      $query = '
477DELETE FROM '.USER_CACHE_CATEGORIES_TABLE.'
478  WHERE user_id = '.$userdata['id'];
479      pwg_query($query);
480
481      // Due to concurrency issues, we ask MySQL to ignore errors on
482      // insert. This may happen when cache needs refresh and that Piwigo is
483      // called "very simultaneously".
484      mass_inserts(
485        USER_CACHE_CATEGORIES_TABLE,
486        array(
487          'user_id', 'cat_id',
488          'date_last', 'max_date_last', 'nb_images', 'count_images', 'nb_categories', 'count_categories'
489          ),
490        $user_cache_cats,
491        array('ignore' => true)
492      );
493
494
495      // update user cache
496      $query = '
497DELETE FROM '.USER_CACHE_TABLE.'
498  WHERE user_id = '.$userdata['id'];
499      pwg_query($query);
500
501      // for the same reason as user_cache_categories, we ignore error on
502      // this insert
503      $query = '
504INSERT IGNORE INTO '.USER_CACHE_TABLE.'
505  (user_id, need_update, cache_update_time, forbidden_categories, nb_total_images,
506    last_photo_date,
507    image_access_type, image_access_list)
508  VALUES
509  ('.$userdata['id'].',\''.boolean_to_string($userdata['need_update']).'\','
510  .$userdata['cache_update_time'].',\''
511  .$userdata['forbidden_categories'].'\','.$userdata['nb_total_images'].','.
512  (empty($userdata['last_photo_date']) ? 'NULL': '\''.$userdata['last_photo_date'].'\'').
513  ',\''.$userdata['image_access_type'].'\',\''.$userdata['image_access_list'].'\')';
514      pwg_query($query);
515    }
516  }
517
518  return $userdata;
519}
520
521/**
522 * Deletes favorites of the current user if he's not allowed to see them.
523 */
524function check_user_favorites()
525{
526  global $user;
527
528  if ($user['forbidden_categories'] == '')
529  {
530    return;
531  }
532
533  // $filter['visible_categories'] and $filter['visible_images']
534  // must be not used because filter <> restriction
535  // retrieving images allowed : belonging to at least one authorized
536  // category
537  $query = '
538SELECT DISTINCT f.image_id
539  FROM '.FAVORITES_TABLE.' AS f INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic
540    ON f.image_id = ic.image_id
541  WHERE f.user_id = '.$user['id'].'
542  '.get_sql_condition_FandF(
543      array(
544        'forbidden_categories' => 'ic.category_id',
545        ),
546      'AND'
547    ).'
548;';
549  $authorizeds = query2array($query,null, 'image_id');
550
551  $query = '
552SELECT image_id
553  FROM '.FAVORITES_TABLE.'
554  WHERE user_id = '.$user['id'].'
555;';
556  $favorites = query2array($query,null, 'image_id');
557
558  $to_deletes = array_diff($favorites, $authorizeds);
559  if (count($to_deletes) > 0)
560  {
561    $query = '
562DELETE FROM '.FAVORITES_TABLE.'
563  WHERE image_id IN ('.implode(',', $to_deletes).')
564    AND user_id = '.$user['id'].'
565;';
566    pwg_query($query);
567  }
568}
569
570/**
571 * Calculates the list of forbidden categories for a given user.
572 *
573 * Calculation is based on private categories minus categories authorized to
574 * the groups the user belongs to minus the categories directly authorized
575 * to the user. The list contains at least 0 to be compliant with queries
576 * such as "WHERE category_id NOT IN ($forbidden_categories)"
577 *
578 * @param int $user_id
579 * @param string $user_status
580 * @return string comma separated ids
581 */
582function calculate_permissions($user_id, $user_status)
583{
584  $query = '
585SELECT id
586  FROM '.CATEGORIES_TABLE.'
587  WHERE status = \'private\'
588;';
589  $private_array = query2array($query,null, 'id');
590
591  // retrieve category ids directly authorized to the user
592  $query = '
593SELECT cat_id
594  FROM '.USER_ACCESS_TABLE.'
595  WHERE user_id = '.$user_id.'
596;';
597  $authorized_array = query2array($query,null, 'cat_id');
598
599  // retrieve category ids authorized to the groups the user belongs to
600  $query = '
601SELECT cat_id
602  FROM '.USER_GROUP_TABLE.' AS ug INNER JOIN '.GROUP_ACCESS_TABLE.' AS ga
603    ON ug.group_id = ga.group_id
604  WHERE ug.user_id = '.$user_id.'
605;';
606  $authorized_array =
607    array_merge(
608      $authorized_array,
609      query2array($query,null, 'cat_id')
610      );
611
612  // uniquify ids : some private categories might be authorized for the
613  // groups and for the user
614  $authorized_array = array_unique($authorized_array);
615
616  // only unauthorized private categories are forbidden
617  $forbidden_array = array_diff($private_array, $authorized_array);
618
619  // if user is not an admin, locked categories are forbidden
620  if (!is_admin($user_status))
621  {
622    $query = '
623SELECT id
624  FROM '.CATEGORIES_TABLE.'
625  WHERE visible = \'false\'
626;';
627    $forbidden_array = array_merge($forbidden_array, query2array($query, null, 'id') );
628    $forbidden_array = array_unique($forbidden_array);
629  }
630
631  if ( empty($forbidden_array) )
632  {// at least, the list contains 0 value. This category does not exists so
633   // where clauses such as "WHERE category_id NOT IN(0)" will always be
634   // true.
635    $forbidden_array[] = 0;
636  }
637
638  return implode(',', $forbidden_array);
639}
640
641/**
642 * Returns user identifier thanks to his name.
643 *
644 * @param string $username
645 * @param int|false
646 */
647function get_userid($username)
648{
649  global $conf;
650
651  $username = pwg_db_real_escape_string($username);
652
653  $query = '
654SELECT '.$conf['user_fields']['id'].'
655  FROM '.USERS_TABLE.'
656  WHERE '.$conf['user_fields']['username'].' = \''.$username.'\'
657;';
658  $result = pwg_query($query);
659
660  if (pwg_db_num_rows($result) == 0)
661  {
662    return false;
663  }
664  else
665  {
666    list($user_id) = pwg_db_fetch_row($result);
667    return $user_id;
668  }
669}
670
671/**
672 * Returns user identifier thanks to his email.
673 *
674 * @param string $email
675 * @param int|false
676 */
677function get_userid_by_email($email)
678{
679  global $conf;
680
681  $email = pwg_db_real_escape_string($email);
682
683  $query = '
684SELECT
685    '.$conf['user_fields']['id'].'
686  FROM '.USERS_TABLE.'
687  WHERE UPPER('.$conf['user_fields']['email'].') = UPPER(\''.$email.'\')
688;';
689  $result = pwg_query($query);
690
691  if (pwg_db_num_rows($result) == 0)
692  {
693    return false;
694  }
695  else
696  {
697    list($user_id) = pwg_db_fetch_row($result);
698    return $user_id;
699  }
700}
701
702/**
703 * Returns a array with default user valuees.
704 *
705 * @param convert_str ceonferts 'true' and 'false' into booleans
706 * @return array
707 */
708function get_default_user_info($convert_str=true)
709{
710  global $cache, $conf;
711
712  if (!isset($cache['default_user']))
713  {
714    $query = '
715SELECT *
716  FROM '.USER_INFOS_TABLE.'
717  WHERE user_id = '.$conf['default_user_id'].'
718;';
719
720    $result = pwg_query($query);
721
722    if (pwg_db_num_rows($result) > 0)
723    {
724      $cache['default_user'] = pwg_db_fetch_assoc($result);
725
726      unset($cache['default_user']['user_id']);
727      unset($cache['default_user']['status']);
728      unset($cache['default_user']['registration_date']);
729    }
730    else
731    {
732      $cache['default_user'] = false;
733    }
734  }
735
736  if (is_array($cache['default_user']) and $convert_str)
737  {
738    $default_user = $cache['default_user'];
739    foreach ($default_user as &$value)
740    {
741      // If the field is true or false, the variable is transformed into a boolean value.
742      if ($value == 'true')
743      {
744        $value = true;
745      }
746      elseif ($value == 'false')
747      {
748        $value = false;
749      }
750    }
751    return $default_user;
752  }
753  else
754  {
755    return $cache['default_user'];
756  }
757}
758
759/**
760 * Returns a default user value.
761 *
762 * @param string $value_name
763 * @param mixed $default
764 * @return mixed
765 */
766function get_default_user_value($value_name, $default)
767{
768  $default_user = get_default_user_info(true);
769  if ($default_user === false or empty($default_user[$value_name]))
770  {
771    return $default;
772  }
773  else
774  {
775   return $default_user[$value_name];
776  }
777}
778
779/**
780 * Returns the default theme.
781 * If the default theme is not available it returns the first available one.
782 *
783 * @return string
784 */
785function get_default_theme()
786{
787  $theme = get_default_user_value('theme', PHPWG_DEFAULT_TEMPLATE);
788  if (check_theme_installed($theme))
789  {
790    return $theme;
791  }
792
793  // let's find the first available theme
794  $active_themes = array_keys(get_pwg_themes());
795  return $active_themes[0];
796}
797
798/**
799 * Returns the default language.
800 *
801 * @return string
802 */
803function get_default_language()
804{
805  return get_default_user_value('language', PHPWG_DEFAULT_LANGUAGE);
806}
807
808/**
809 * Tries to find the browser language among available languages.
810 * @todo : try to match 'fr_CA' before 'fr'
811 *
812 * @param string &$lang
813 * @return bool
814 */
815function get_browser_language(&$lang)
816{
817  $browser_language = substr(@$_SERVER["HTTP_ACCEPT_LANGUAGE"], 0, 2);
818  foreach (get_languages() as $language_code => $language_name)
819  {
820    if (substr($language_code, 0, 2) == $browser_language)
821    {
822      $lang = $language_code;
823      return true;
824    }
825  }
826  return false;
827}
828
829/**
830 * Creates user informations based on default values.
831 *
832 * @param int|int[] $user_ids
833 * @param array $override_values values used to override default user values
834 */
835function create_user_infos($user_ids, $override_values=null)
836{
837  global $conf;
838
839  if (!is_array($user_ids))
840  {
841    $user_ids = array($user_ids);
842  }
843
844  if (!empty($user_ids))
845  {
846    $inserts = array();
847    list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW();'));
848
849    $default_user = get_default_user_info(false);
850    if ($default_user === false)
851    {
852      // Default on structure are used
853      $default_user = array();
854    }
855
856    if (!is_null($override_values))
857    {
858      $default_user = array_merge($default_user, $override_values);
859    }
860
861    foreach ($user_ids as $user_id)
862    {
863      $level= isset($default_user['level']) ? $default_user['level'] : 0;
864      if ($user_id == $conf['webmaster_id'])
865      {
866        $status = 'webmaster';
867        $level = max( $conf['available_permission_levels'] );
868      }
869      elseif (($user_id == $conf['guest_id']) or
870               ($user_id == $conf['default_user_id']))
871      {
872        $status = 'guest';
873      }
874      else
875      {
876        $status = 'normal';
877      }
878
879      $insert = array_merge(
880        $default_user,
881        array(
882          'user_id' => $user_id,
883          'status' => $status,
884          'registration_date' => $dbnow,
885          'level' => $level
886          ));
887
888      $inserts[] = $insert;
889    }
890
891    mass_inserts(USER_INFOS_TABLE, array_keys($inserts[0]), $inserts);
892  }
893}
894
895/**
896 * Returns the auto login key for an user or false if the user is not found.
897 *
898 * @param int $user_id
899 * @param int $time
900 * @param string &$username fille with corresponding username
901 * @return string|false
902 */
903function calculate_auto_login_key($user_id, $time, &$username)
904{
905  global $conf;
906  $query = '
907SELECT '.$conf['user_fields']['username'].' AS username
908  , '.$conf['user_fields']['password'].' AS password
909FROM '.USERS_TABLE.'
910WHERE '.$conf['user_fields']['id'].' = '.$user_id;
911  $result = pwg_query($query);
912  if (pwg_db_num_rows($result) > 0)
913  {
914    $row = pwg_db_fetch_assoc($result);
915    $username = stripslashes($row['username']);
916    $data = $time.$user_id.$username;
917    $key = base64_encode( hash_hmac('sha1', $data, $conf['secret_key'].$row['password'],true) );
918    return $key;
919  }
920  return false;
921}
922
923/**
924 * Performs all required actions for user login.
925 *
926 * @param int $user_id
927 * @param bool $remember_me
928 */
929function log_user($user_id, $remember_me)
930{
931  global $conf, $user;
932
933  if ($remember_me and $conf['authorize_remembering'])
934  {
935    $now = time();
936    $key = calculate_auto_login_key($user_id, $now, $username);
937    if ($key!==false)
938    {
939      $cookie = $user_id.'-'.$now.'-'.$key;
940      setcookie($conf['remember_me_name'],
941        $cookie,
942        time()+$conf['remember_me_length'],
943        cookie_path(),ini_get('session.cookie_domain'),ini_get('session.cookie_secure'),
944        ini_get('session.cookie_httponly')
945        );
946    }
947  }
948  else
949  { // make sure we clean any remember me ...
950    setcookie($conf['remember_me_name'], '', 0, cookie_path(),ini_get('session.cookie_domain'));
951  }
952  if ( session_id()!="" )
953  { // we regenerate the session for security reasons
954    // see http://www.acros.si/papers/session_fixation.pdf
955    session_regenerate_id(true);
956  }
957  else
958  {
959    session_start();
960  }
961  $_SESSION['pwg_uid'] = (int)$user_id;
962
963  $user['id'] = $_SESSION['pwg_uid'];
964  trigger_notify('user_login', $user['id']);
965}
966
967/**
968 * Performs auto-connection when cookie remember_me exists.
969 *
970 * @return bool
971 */
972function auto_login()
973{
974  global $conf;
975
976  if ( isset( $_COOKIE[$conf['remember_me_name']] ) )
977  {
978    $cookie = explode('-', stripslashes($_COOKIE[$conf['remember_me_name']]));
979    if ( count($cookie)===3
980        and is_numeric(@$cookie[0]) /*user id*/
981        and is_numeric(@$cookie[1]) /*time*/
982        and time()-$conf['remember_me_length']<=@$cookie[1]
983        and time()>=@$cookie[1] /*cookie generated in the past*/ )
984    {
985      $key = calculate_auto_login_key( $cookie[0], $cookie[1], $username );
986      if ($key!==false and $key===$cookie[2])
987      {
988        log_user($cookie[0], true);
989        trigger_notify('login_success', stripslashes($username));
990        return true;
991      }
992    }
993    setcookie($conf['remember_me_name'], '', 0, cookie_path(),ini_get('session.cookie_domain'));
994  }
995  return false;
996}
997
998/**
999 * Hashes a password with the PasswordHash class from phpass security library.
1000 * @since 2.5
1001 *
1002 * @param string $password plain text
1003 * @return string
1004 */
1005function pwg_password_hash($password)
1006{
1007  global $pwg_hasher;
1008
1009  if (empty($pwg_hasher))
1010  {
1011    require_once(PHPWG_ROOT_PATH.'include/passwordhash.class.php');
1012
1013    // We use the portable hash feature from phpass because we can't be sure
1014    // Piwigo runs on PHP 5.3+ (and won't run on an older version in the
1015    // future)
1016    $pwg_hasher = new PasswordHash(13, true);
1017  }
1018
1019  return $pwg_hasher->HashPassword($password);
1020}
1021
1022/**
1023 * Verifies a password, with the PasswordHash class from phpass security library.
1024 * If the hash is 'old' (assumed MD5) the hash is updated in database, used for
1025 * migration from Piwigo 2.4.
1026 * @since 2.5
1027 *
1028 * @param string $password plain text
1029 * @param string $hash may be md5 or phpass hashed password
1030 * @param integer $user_id only useful to update password hash from md5 to phpass
1031 * @return bool
1032 */
1033function pwg_password_verify($password, $hash, $user_id=null)
1034{
1035  global $conf, $pwg_hasher;
1036
1037  // If the password has not been hashed with the current algorithm.
1038  if (strpos($hash, '$P') !== 0)
1039  {
1040    if (!empty($conf['pass_convert']))
1041    {
1042      $check = ($hash == $conf['pass_convert']($password));
1043    }
1044    else
1045    {
1046      $check = ($hash == md5($password));
1047    }
1048
1049    if ($check)
1050    {
1051      if (!isset($user_id) or $conf['external_authentification'])
1052      {
1053        return true;
1054      }
1055
1056      // Rehash using new hash.
1057      $hash = pwg_password_hash($password);
1058
1059      single_update(
1060        USERS_TABLE,
1061        array('password' => $hash),
1062        array('id' => $user_id)
1063        );
1064    }
1065  }
1066
1067  // If the stored hash is longer than an MD5, presume the
1068  // new style phpass portable hash.
1069  if (empty($pwg_hasher))
1070  {
1071    require_once(PHPWG_ROOT_PATH.'include/passwordhash.class.php');
1072
1073    // We use the portable hash feature
1074    $pwg_hasher = new PasswordHash(13, true);
1075  }
1076
1077  return $pwg_hasher->CheckPassword($password, $hash);
1078}
1079
1080/**
1081 * Tries to login a user given username and password (must be MySql escaped).
1082 *
1083 * @param string $username
1084 * @param string $password
1085 * @param bool $remember_me
1086 * @return bool
1087 */
1088function try_log_user($username, $password, $remember_me)
1089{
1090  return trigger_change('try_log_user', false, $username, $password, $remember_me);
1091}
1092
1093add_event_handler('try_log_user', 'pwg_login');
1094
1095/**
1096 * Default method for user login, can be overwritten with 'try_log_user' trigger.
1097 * @see try_log_user()
1098 *
1099 * @param string $username
1100 * @param string $password
1101 * @param bool $remember_me
1102 * @return bool
1103 */
1104function pwg_login($success, $username, $password, $remember_me)
1105{
1106  if ($success===true)
1107  {
1108    return true;
1109  }
1110
1111  // we force the session table to be clean
1112  pwg_session_gc();
1113
1114  global $conf;
1115  // retrieving the encrypted password of the login submitted
1116  $query = '
1117SELECT '.$conf['user_fields']['id'].' AS id,
1118       '.$conf['user_fields']['password'].' AS password
1119  FROM '.USERS_TABLE.'
1120  WHERE '.$conf['user_fields']['username'].' = \''.pwg_db_real_escape_string($username).'\'
1121;';
1122  $row = pwg_db_fetch_assoc(pwg_query($query));
1123  if ($conf['password_verify']($password, $row['password'], $row['id']))
1124  {
1125    log_user($row['id'], $remember_me);
1126    trigger_notify('login_success', stripslashes($username));
1127    return true;
1128  }
1129  trigger_notify('login_failure', stripslashes($username));
1130  return false;
1131}
1132
1133/**
1134 * Performs all the cleanup on user logout.
1135 */
1136function logout_user()
1137{
1138  global $conf;
1139
1140  trigger_notify('user_logout', @$_SESSION['pwg_uid']);
1141
1142  $_SESSION = array();
1143  session_unset();
1144  session_destroy();
1145  setcookie(session_name(),'',0,
1146      ini_get('session.cookie_path'),
1147      ini_get('session.cookie_domain')
1148    );
1149  setcookie($conf['remember_me_name'], '', 0, cookie_path(),ini_get('session.cookie_domain'));
1150}
1151
1152/**
1153 * Return user status.
1154 *
1155 * @param string $user_status used if $user not initialized
1156 * @return string
1157 */
1158function get_user_status($user_status='')
1159{
1160  global $user;
1161
1162  if (empty($user_status))
1163  {
1164    if (isset($user['status']))
1165    {
1166      $user_status = $user['status'];
1167    }
1168    else
1169    {
1170      // swicth to default value
1171      $user_status = '';
1172    }
1173  }
1174  return $user_status;
1175}
1176
1177/**
1178 * Return ACCESS_* value for a given $status.
1179 *
1180 * @param string $user_status used if $user not initialized
1181 * @return int one of ACCESS_* constants
1182 */
1183function get_access_type_status($user_status='')
1184{
1185  global $conf;
1186
1187  switch (get_user_status($user_status))
1188  {
1189    case 'guest':
1190    {
1191      $access_type_status =
1192        ($conf['guest_access'] ? ACCESS_GUEST : ACCESS_FREE);
1193      break;
1194    }
1195    case 'generic':
1196    {
1197      $access_type_status = ACCESS_GUEST;
1198      break;
1199    }
1200    case 'normal':
1201    {
1202      $access_type_status = ACCESS_CLASSIC;
1203      break;
1204    }
1205    case 'admin':
1206    {
1207      $access_type_status = ACCESS_ADMINISTRATOR;
1208      break;
1209    }
1210    case 'webmaster':
1211    {
1212      $access_type_status = ACCESS_WEBMASTER;
1213      break;
1214    }
1215    default:
1216    {
1217      $access_type_status = ACCESS_FREE;
1218      break;
1219    }
1220  }
1221
1222  return $access_type_status;
1223}
1224
1225/**
1226 * Returns if user has access to a particular ACCESS_*
1227 *
1228 * @return int $access_type one of ACCESS_* constants
1229 * @param string $user_status used if $user not initialized
1230 * @return bool
1231 */
1232function is_autorize_status($access_type, $user_status='')
1233{
1234  return (get_access_type_status($user_status) >= $access_type);
1235}
1236
1237/**
1238 * Abord script if user has no access to a particular ACCESS_*
1239 *
1240 * @return int $access_type one of ACCESS_* constants
1241 * @param string $user_status used if $user not initialized
1242 */
1243function check_status($access_type, $user_status='')
1244{
1245  if (!is_autorize_status($access_type, $user_status))
1246  {
1247    access_denied();
1248  }
1249}
1250
1251/**
1252 * Returns if user is generic.
1253 *
1254 * @param string $user_status used if $user not initialized
1255 * @return bool
1256 */
1257function is_generic($user_status='')
1258{
1259  return get_user_status($user_status) == 'generic';
1260}
1261
1262/**
1263 * Returns if user is a guest.
1264 *
1265 * @param string $user_status used if $user not initialized
1266 * @return bool
1267 */
1268function is_a_guest($user_status='')
1269{
1270  return get_user_status($user_status) == 'guest';
1271}
1272
1273/**
1274 * Returns if user is, at least, a classic user.
1275 *
1276 * @param string $user_status used if $user not initialized
1277 * @return bool
1278 */
1279function is_classic_user($user_status='')
1280{
1281  return is_autorize_status(ACCESS_CLASSIC, $user_status);
1282}
1283
1284/**
1285 * Returns if user is, at least, an administrator.
1286 *
1287 * @param string $user_status used if $user not initialized
1288 * @return bool
1289 */
1290function is_admin($user_status='')
1291{
1292  return is_autorize_status(ACCESS_ADMINISTRATOR, $user_status);
1293}
1294
1295/**
1296 * Returns if user is a webmaster.
1297 *
1298 * @param string $user_status used if $user not initialized
1299 * @return bool
1300 */
1301function is_webmaster($user_status='')
1302{
1303  return is_autorize_status(ACCESS_WEBMASTER, $user_status);
1304}
1305
1306/**
1307 * Returns if current user can edit/delete/validate a comment.
1308 *
1309 * @param string $action edit/delete/validate
1310 * @param int $comment_author_id
1311 * @return bool
1312 */
1313function can_manage_comment($action, $comment_author_id)
1314{
1315  global $user, $conf;
1316
1317  if (is_a_guest())
1318  {
1319    return false;
1320  }
1321
1322  if (!in_array($action, array('delete','edit', 'validate')))
1323  {
1324    return false;
1325  }
1326
1327  if (is_admin())
1328  {
1329    return true;
1330  }
1331
1332  if ('edit' == $action and $conf['user_can_edit_comment'])
1333  {
1334    if ($comment_author_id == $user['id']) {
1335      return true;
1336    }
1337  }
1338
1339  if ('delete' == $action and $conf['user_can_delete_comment'])
1340  {
1341    if ($comment_author_id == $user['id']) {
1342      return true;
1343    }
1344  }
1345
1346  return false;
1347}
1348
1349/**
1350 * Compute sql WHERE condition with restrict and filter data.
1351 * "FandF" means Forbidden and Filters.
1352 *
1353 * @param array $condition_fields one witch fields apply each filter
1354 *    - forbidden_categories
1355 *    - visible_categories
1356 *    - forbidden_images
1357 *    - visible_images
1358 * @param string $prefix_condition prefixes query if condition is not empty
1359 * @param boolean $force_one_condition use at least "1 = 1"
1360 * @return string
1361 */
1362function get_sql_condition_FandF(
1363  $condition_fields,
1364  $prefix_condition = null,
1365  $force_one_condition = false
1366  )
1367{
1368  global $user, $filter;
1369
1370  $sql_list = array();
1371
1372  foreach ($condition_fields as $condition => $field_name)
1373  {
1374    switch($condition)
1375    {
1376      case 'forbidden_categories':
1377      {
1378        if (!empty($user['forbidden_categories']))
1379        {
1380          $sql_list[] =
1381            $field_name.' NOT IN ('.$user['forbidden_categories'].')';
1382        }
1383        break;
1384      }
1385      case 'visible_categories':
1386      {
1387        if (!empty($filter['visible_categories']))
1388        {
1389          $sql_list[] =
1390            $field_name.' IN ('.$filter['visible_categories'].')';
1391        }
1392        break;
1393      }
1394      case 'visible_images':
1395        if (!empty($filter['visible_images']))
1396        {
1397          $sql_list[] =
1398            $field_name.' IN ('.$filter['visible_images'].')';
1399        }
1400        // note there is no break - visible include forbidden
1401      case 'forbidden_images':
1402        if (
1403            !empty($user['image_access_list'])
1404            or $user['image_access_type']!='NOT IN'
1405            )
1406        {
1407          $table_prefix=null;
1408          if ($field_name=='id')
1409          {
1410            $table_prefix = '';
1411          }
1412          elseif ($field_name=='i.id')
1413          {
1414            $table_prefix = 'i.';
1415          }
1416          if ( isset($table_prefix) )
1417          {
1418            $sql_list[]=$table_prefix.'level<='.$user['level'];
1419          }
1420          elseif ( !empty($user['image_access_list']) and !empty($user['image_access_type']) )
1421          {
1422            $sql_list[]=$field_name.' '.$user['image_access_type']
1423                .' ('.$user['image_access_list'].')';
1424          }
1425        }
1426        break;
1427      default:
1428      {
1429        die('Unknow condition');
1430        break;
1431      }
1432    }
1433  }
1434
1435  if (count($sql_list) > 0)
1436  {
1437    $sql = '('.implode(' AND ', $sql_list).')';
1438  }
1439  else
1440  {
1441    $sql = $force_one_condition ? '1 = 1' : '';
1442  }
1443
1444  if (isset($prefix_condition) and !empty($sql))
1445  {
1446    $sql = $prefix_condition.' '.$sql;
1447  }
1448
1449  return $sql;
1450}
1451
1452/**
1453 * Returns sql WHERE condition for recent photos/albums for current user.
1454 *
1455 * @param string $db_field
1456 * @return string
1457 */
1458function get_recent_photos_sql($db_field)
1459{
1460  global $user;
1461  if (!isset($user['last_photo_date']))
1462  {
1463    return '0=1';
1464  }
1465  return $db_field.'>=LEAST('
1466    .pwg_db_get_recent_period_expression($user['recent_period'])
1467    .','.pwg_db_get_recent_period_expression(1,$user['last_photo_date']).')';
1468}
1469?>
Note: See TracBrowser for help on using the repository browser.