]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Users/User.php
Release 6.5.6
[Github/sugarcrm.git] / modules / Users / User.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40  * Description: TODO:  To be written.
41  * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
42  * All Rights Reserved.
43  * Contributor(s): ______________________________________..
44  ********************************************************************************/
45
46 require_once('include/SugarObjects/templates/person/Person.php');
47
48
49 // User is used to store customer information.
50 class User extends Person {
51         // Stored fields
52         var $name = '';
53         var $full_name;
54         var $id;
55         var $user_name;
56         var $user_hash;
57         var $salutation;
58         var $first_name;
59         var $last_name;
60         var $date_entered;
61         var $date_modified;
62         var $modified_user_id;
63         var $created_by;
64         var $created_by_name;
65         var $modified_by_name;
66         var $description;
67         var $phone_home;
68         var $phone_mobile;
69         var $phone_work;
70         var $phone_other;
71         var $phone_fax;
72         var $email1;
73         var $email2;
74         var $address_street;
75         var $address_city;
76         var $address_state;
77         var $address_postalcode;
78         var $address_country;
79         var $status;
80         var $title;
81         var $portal_only;
82         var $department;
83         var $authenticated = false;
84         var $error_string;
85         var $is_admin;
86         var $employee_status;
87         var $messenger_id;
88         var $messenger_type;
89         var $is_group;
90         var $accept_status; // to support Meetings
91         //adding a property called team_id so we can populate it for use in the team widget
92         var $team_id;
93
94         var $receive_notifications;
95
96         var $reports_to_name;
97         var $reports_to_id;
98         var $team_exists = false;
99         var $table_name = "users";
100         var $module_dir = 'Users';
101         var $object_name = "User";
102         var $user_preferences;
103
104         var $importable = true;
105         var $_userPreferenceFocus;
106
107         var $encodeFields = Array ("first_name", "last_name", "description");
108
109         // This is used to retrieve related fields from form posts.
110         var $additional_column_fields = array ('reports_to_name'
111         );
112
113         var $emailAddress;
114
115
116         var $new_schema = true;
117
118         function User() {
119                 parent::Person();
120
121                 $this->_loadUserPreferencesFocus();
122         }
123
124         protected function _loadUserPreferencesFocus()
125         {
126             $this->_userPreferenceFocus = new UserPreference($this);
127         }
128
129     /**
130      * returns an admin user
131      */
132     public function getSystemUser()
133     {
134         if (null === $this->retrieve('1'))
135             // handle cases where someone deleted user with id "1"
136             $this->retrieve_by_string_fields(array(
137                 'status' => 'Active',
138                 'is_admin' => '1',
139                 ));
140
141         return $this;
142     }
143
144
145         /**
146          * convenience function to get user's default signature
147          */
148         function getDefaultSignature() {
149                 if($defaultId = $this->getPreference('signature_default')) {
150                         return $this->getSignature($defaultId);
151                 } else {
152                         return array();
153                 }
154         }
155
156         /**
157          * retrieves the signatures for a user
158          * @param string id ID of user_signature
159          * @return array ID, signature, and signature_html
160          */
161         public function getSignature($id)
162         {
163             $signatures = $this->getSignaturesArray();
164
165             return isset($signatures[$id]) ? $signatures[$id] : FALSE;
166         }
167
168         function getSignaturesArray() {
169                 $q = 'SELECT * FROM users_signatures WHERE user_id = \''.$this->id.'\' AND deleted = 0 ORDER BY name ASC';
170                 $r = $this->db->query($q);
171
172                 // provide "none"
173                 $sig = array(""=>"");
174
175                 while($a = $this->db->fetchByAssoc($r)) {
176                         $sig[$a['id']] = $a;
177                 }
178
179                 return $sig;
180         }
181
182         /**
183          * retrieves any signatures that the User may have created as <select>
184          */
185         public function getSignatures(
186             $live = false,
187             $defaultSig = '',
188             $forSettings = false
189             )
190         {
191                 $sig = $this->getSignaturesArray();
192                 $sigs = array();
193                 foreach ($sig as $key => $arr)
194                 {
195                         $sigs[$key] = !empty($arr['name']) ? $arr['name'] : '';
196                 }
197
198                 $change = '';
199                 if(!$live) {
200                         $change = ($forSettings) ? "onChange='displaySignatureEdit();'" : "onChange='setSigEditButtonVisibility();'";
201                 }
202
203                 $id = (!$forSettings) ? 'signature_id' : 'signature_idDisplay';
204
205                 $out  = "<select {$change} id='{$id}' name='{$id}'>";
206                 $out .= get_select_options_with_id($sigs, $defaultSig).'</select>';
207
208                 return $out;
209         }
210
211         /**
212          * returns buttons and JS for signatures
213          */
214         function getSignatureButtons($jscall='', $defaultDisplay=false) {
215                 global $mod_strings;
216
217                 $jscall = empty($jscall) ? 'open_email_signature_form' : $jscall;
218
219                 $butts  = "<input class='button' onclick='javascript:{$jscall}(\"\", \"{$this->id}\");' value='{$mod_strings['LBL_BUTTON_CREATE']}' type='button'>&nbsp;";
220                 if($defaultDisplay) {
221                         $butts .= '<span name="edit_sig" id="edit_sig" style="visibility:inherit;"><input class="button" onclick="javascript:'.$jscall.'(document.getElementById(\'signature_id\', \'\').value)" value="'.$mod_strings['LBL_BUTTON_EDIT'].'" type="button" tabindex="392">&nbsp;
222                                         </span>';
223                 } else {
224                         $butts .= '<span name="edit_sig" id="edit_sig" style="visibility:hidden;"><input class="button" onclick="javascript:'.$jscall.'(document.getElementById(\'signature_id\', \'\').value)" value="'.$mod_strings['LBL_BUTTON_EDIT'].'" type="button" tabindex="392">&nbsp;
225                                         </span>';
226                 }
227                 return $butts;
228         }
229
230         /**
231          * performs a rudimentary check to verify if a given user has setup personal
232          * InboundEmail
233          *
234          * @return bool
235          */
236         public function hasPersonalEmail()
237         {
238             $focus = new InboundEmail;
239             $focus->retrieve_by_string_fields(array('group_id' => $this->id));
240
241             return !empty($focus->id);
242         }
243
244         /* Returns the User's private GUID; this is unassociated with the User's
245          * actual GUID.  It is used to secure file names that must be HTTP://
246          * accesible, but obfusicated.
247          */
248         function getUserPrivGuid() {
249         $userPrivGuid = $this->getPreference('userPrivGuid', 'global', $this);
250                 if ($userPrivGuid) {
251                         return $userPrivGuid;
252                 } else {
253                         $this->setUserPrivGuid();
254                         if (!isset ($_SESSION['setPrivGuid'])) {
255                                 $_SESSION['setPrivGuid'] = true;
256                                 $userPrivGuid = $this->getUserPrivGuid();
257                                 return $userPrivGuid;
258                         } else {
259                                 sugar_die("Breaking Infinite Loop Condition: Could not setUserPrivGuid.");
260                         }
261                 }
262         }
263
264         function setUserPrivGuid() {
265                 $privGuid = create_guid();
266                 //($name, $value, $nosession=0)
267                 $this->setPreference('userPrivGuid', $privGuid, 0, 'global', $this);
268         }
269
270         /**
271          * Interface for the User object to calling the UserPreference::setPreference() method in modules/UserPreferences/UserPreference.php
272          *
273          * @see UserPreference::setPreference()
274          *
275          * @param string $name Name of the preference to set
276          * @param string $value Value to set preference to
277          * @param null $nosession For BC, ignored
278          * @param string $category Name of the category to retrieve
279          */
280         public function setPreference(
281             $name,
282             $value,
283             $nosession = 0,
284             $category = 'global'
285             )
286         {
287             // for BC
288             if ( func_num_args() > 4 ) {
289                 $user = func_get_arg(4);
290                 $GLOBALS['log']->deprecated('User::setPreferences() should not be used statically.');
291             }
292             else
293                 $user = $this;
294
295         $user->_userPreferenceFocus->setPreference($name, $value, $category);
296         }
297
298         /**
299          * Interface for the User object to calling the UserPreference::resetPreferences() method in modules/UserPreferences/UserPreference.php
300          *
301          * @see UserPreference::resetPreferences()
302          *
303          * @param string $category category to reset
304          */
305         public function resetPreferences(
306             $category = null
307             )
308         {
309             // for BC
310             if ( func_num_args() > 1 ) {
311                 $user = func_get_arg(1);
312                 $GLOBALS['log']->deprecated('User::resetPreferences() should not be used statically.');
313             }
314             else
315                 $user = $this;
316
317         $user->_userPreferenceFocus->resetPreferences($category);
318         }
319
320         /**
321          * Interface for the User object to calling the UserPreference::savePreferencesToDB() method in modules/UserPreferences/UserPreference.php
322          *
323          * @see UserPreference::savePreferencesToDB()
324          */
325         public function savePreferencesToDB()
326         {
327         // for BC
328             if ( func_num_args() > 0 ) {
329                 $user = func_get_arg(0);
330                 $GLOBALS['log']->deprecated('User::savePreferencesToDB() should not be used statically.');
331             }
332             else
333                 $user = $this;
334
335         $user->_userPreferenceFocus->savePreferencesToDB();
336         }
337
338         /**
339          * Unconditionally reloads user preferences from the DB and updates the session
340          * @param string $category name of the category to retreive, defaults to global scope
341          * @return bool successful?
342          */
343         public function reloadPreferences($category = 'global')
344         {
345             return $this->_userPreferenceFocus->reloadPreferences($category = 'global');
346         }
347
348         /**
349          * Interface for the User object to calling the UserPreference::getUserDateTimePreferences() method in modules/UserPreferences/UserPreference.php
350          *
351          * @see UserPreference::getUserDateTimePreferences()
352          *
353          * @return array 'date' - date format for user ; 'time' - time format for user
354          */
355         public function getUserDateTimePreferences()
356         {
357         // for BC
358             if ( func_num_args() > 0 ) {
359                 $user = func_get_arg(0);
360                 $GLOBALS['log']->deprecated('User::getUserDateTimePreferences() should not be used statically.');
361             }
362             else
363                 $user = $this;
364
365         return $user->_userPreferenceFocus->getUserDateTimePreferences();
366         }
367
368         /**
369          * Interface for the User object to calling the UserPreference::loadPreferences() method in modules/UserPreferences/UserPreference.php
370          *
371          * @see UserPreference::loadPreferences()
372          *
373          * @param string $category name of the category to retreive, defaults to global scope
374          * @return bool successful?
375          */
376         public function loadPreferences(
377             $category = 'global'
378             )
379         {
380             // for BC
381             if ( func_num_args() > 1 ) {
382                 $user = func_get_arg(1);
383                 $GLOBALS['log']->deprecated('User::loadPreferences() should not be used statically.');
384             }
385             else
386                 $user = $this;
387
388         return $user->_userPreferenceFocus->loadPreferences($category);
389         }
390
391         /**
392          * Interface for the User object to calling the UserPreference::setPreference() method in modules/UserPreferences/UserPreference.php
393          *
394          * @see UserPreference::getPreference()
395          *
396          * @param string $name name of the preference to retreive
397          * @param string $category name of the category to retreive, defaults to global scope
398          * @return mixed the value of the preference (string, array, int etc)
399          */
400         public function getPreference(
401             $name,
402             $category = 'global'
403             )
404         {
405             // for BC
406             if ( func_num_args() > 2 ) {
407                 $user = func_get_arg(2);
408                 $GLOBALS['log']->deprecated('User::getPreference() should not be used statically.');
409             }
410             else
411                 $user = $this;
412
413         return $user->_userPreferenceFocus->getPreference($name, $category);
414         }
415
416         /**
417      * incrementETag
418      *
419      * This function increments any ETag seed needed for a particular user's
420      * UI. For example, if the user changes their theme, the ETag seed for the
421      * main menu needs to be updated, so you call this function with the seed name
422      * to do so:
423      *
424      * UserPreference::incrementETag("mainMenuETag");
425      *
426      * @param string $tag ETag seed name.
427      * @return nothing
428      */
429     public function incrementETag($tag){
430         $val = $this->getETagSeed($tag);
431         if($val == 2147483648){
432                 $val = 0;
433         }
434         $val++;
435         $this->setPreference($tag, $val, 0, "ETag");
436     }
437
438     /**
439      * getETagSeed
440      *
441      * This function is a wrapper to encapsulate getting the ETag seed and
442      * making sure it's sanitized for use in the app.
443      *
444      * @param string $tag ETag seed name.
445      * @return integer numeric value of the seed
446      */
447     public function getETagSeed($tag){
448         $val = $this->getPreference($tag, "ETag");
449         if($val == null){
450                 $val = 0;
451         }
452         return $val;
453     }
454
455
456    /**
457     * Get WHERE clause that fetches all users counted for licensing purposes
458     * @return string
459     */
460         public static function getLicensedUsersWhere()
461         {
462                 return "deleted=0 AND status='Active' AND user_name IS NOT NULL AND is_group=0 AND portal_only=0  AND ".$GLOBALS['db']->convert('user_name', 'length').">0";
463             return "1<>1";
464         }
465
466         function save($check_notify = false) {
467                 $isUpdate = !empty($this->id) && !$this->new_with_id;
468
469
470                 $query = "SELECT count(id) as total from users WHERE ".self::getLicensedUsersWhere();
471
472
473                 // wp: do not save user_preferences in this table, see user_preferences module
474                 $this->user_preferences = '';
475
476                 // if this is an admin user, do not allow is_group or portal_only flag to be set.
477                 if ($this->is_admin) {
478                         $this->is_group = 0;
479                         $this->portal_only = 0;
480                 }
481
482
483                 // set some default preferences when creating a new user
484                 $setNewUserPreferences = empty($this->id) || !empty($this->new_with_id);
485
486
487
488                 parent::save($check_notify);
489
490
491                 // set some default preferences when creating a new user
492                 if ( $setNewUserPreferences ) {
493                 if(!$this->getPreference('calendar_publish_key')) {
494                         $this->setPreference('calendar_publish_key', create_guid());
495                 }
496                 }
497
498         $this->savePreferencesToDB();
499         return $this->id;
500         }
501
502         /**
503         * @return boolean true if the user is a member of the role_name, false otherwise
504         * @param string $role_name - Must be the exact name of the acl_role
505         * @param string $user_id - The user id to check for the role membership, empty string if current user
506         * @desc Determine whether or not a user is a member of an ACL Role. This function caches the
507         *       results in the session or to prevent running queries after the first time executed.
508         * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc..
509         * All Rights Reserved..
510         * Contributor(s): ______________________________________..
511         */
512         function check_role_membership($role_name, $user_id = ''){
513
514                 global $current_user;
515
516                 if(empty($user_id))
517                         $user_id = $current_user->id;
518
519                 // Check the Sugar External Cache to see if this users memberships were cached
520                 $role_array = sugar_cache_retrieve("RoleMemberships_".$user_id);
521
522                 // If we are pulling the roles for the current user
523                 if($user_id == $current_user->id){
524                         // If the Session doesn't contain the values
525                         if(!isset($_SESSION['role_memberships'])){
526                                 // This means the external cache already had it loaded
527                                 if(!empty($role_array))
528                                         $_SESSION['role_memberships'] = $role_array;
529                                 else{
530                                         $_SESSION['role_memberships'] = ACLRole::getUserRoleNames($user_id);
531                                         $role_array = $_SESSION['role_memberships'];
532                                 }
533                         }
534                         // else the session had the values, so we assign to the role array
535                         else{
536                                 $role_array = $_SESSION['role_memberships'];
537                         }
538                 }
539                 else{
540                         // If the external cache didn't contain the values, we get them and put them in cache
541                         if(!$role_array){
542                                 $role_array = ACLRole::getUserRoleNames($user_id);
543                                 sugar_cache_put("RoleMemberships_".$user_id, $role_array);
544                         }
545                 }
546
547                 // If the role doesn't exist in the list of the user's roles
548                 if(!empty($role_array) && in_array($role_name, $role_array))
549                         return true;
550                 else
551                         return false;
552         }
553
554     function get_summary_text() {
555         //$this->_create_proper_name_field();
556         return $this->name;
557         }
558
559         /**
560          * @deprecated
561         * @param string $user_name - Must be non null and at least 2 characters
562         * @param string $user_password - Must be non null and at least 1 character.
563         * @desc Take an unencrypted username and password and return the encrypted password
564         * @return string encrypted password for storage in DB and comparison against DB password.
565         */
566         function encrypt_password($user_password)
567         {
568                 // encrypt the password.
569                 $salt = substr($this->user_name, 0, 2);
570                 $encrypted_password = crypt($user_password, $salt);
571
572                 return $encrypted_password;
573         }
574
575         /**
576          * Authenicates the user; returns true if successful
577          *
578          * @param string $password MD5-encoded password
579          * @return bool
580          */
581         public function authenticate_user($password)
582         {
583             $row = self::findUserPassword($this->user_name, $password);
584             if(empty($row)) {
585                 return false;
586                 } else {
587                         $this->id = $row['id'];
588                         return true;
589                 }
590         }
591
592     /**
593      * retrieves an User bean
594      * preformat name & full_name attribute with first/last
595      * loads User's preferences
596      *
597      * @param string id ID of the User
598      * @param bool encode encode the result
599      * @return object User bean
600      * @return null null if no User found
601      */
602         function retrieve($id, $encode = true, $deleted = true) {
603                 $ret = parent::retrieve($id, $encode, $deleted);
604                 if ($ret) {
605                         if (isset ($_SESSION)) {
606                                 $this->loadPreferences();
607                         }
608                 }
609                 return $ret;
610         }
611
612         function retrieve_by_email_address($email) {
613
614                 $email1= strtoupper($email);
615                 $q=<<<EOQ
616
617                 select id from users where id in ( SELECT  er.bean_id AS id FROM email_addr_bean_rel er,
618                         email_addresses ea WHERE ea.id = er.email_address_id
619                     AND ea.deleted = 0 AND er.deleted = 0 AND er.bean_module = 'Users' AND email_address_caps IN ('{$email1}') )
620 EOQ;
621
622
623                 $res=$this->db->query($q);
624                 $row=$this->db->fetchByAssoc($res);
625
626                 if (!empty($row['id'])) {
627                         return $this->retrieve($row['id']);
628                 }
629                 return '';
630         }
631
632    function bean_implements($interface) {
633         switch($interface){
634             case 'ACL':return true;
635         }
636         return false;
637     }
638
639
640         /**
641          * Load a user based on the user_name in $this
642          * @param string $user_password Password
643          * @param bool $password_encoded Is password md5-encoded or plain text?
644          * @return -- this if load was successul and null if load failed.
645          */
646         function load_user($user_password, $password_encoded = false)
647         {
648                 global $login_error;
649                 unset($GLOBALS['login_error']);
650                 if(isset ($_SESSION['loginattempts'])) {
651                         $_SESSION['loginattempts'] += 1;
652                 } else {
653                         $_SESSION['loginattempts'] = 1;
654                 }
655                 if($_SESSION['loginattempts'] > 5) {
656                         $GLOBALS['log']->fatal('SECURITY: '.$this->user_name.' has attempted to login '.$_SESSION['loginattempts'].' times from IP address: '.$_SERVER['REMOTE_ADDR'].'.');
657                         return null;
658                 }
659
660                 $GLOBALS['log']->debug("Starting user load for $this->user_name");
661
662                 if (!isset ($this->user_name) || $this->user_name == "" || !isset ($user_password) || $user_password == "")
663                         return null;
664
665             if(!$password_encoded) {
666                 $user_password = md5($user_password);
667             }
668         $row = self::findUserPassword($this->user_name, $user_password);
669                 if(empty($row) || !empty ($GLOBALS['login_error'])) {
670                         $GLOBALS['log']->fatal('SECURITY: User authentication for '.$this->user_name.' failed - could not Load User from Database');
671                         return null;
672                 }
673
674                 // now fill in the fields.
675                 $this->loadFromRow($row);
676                 $this->loadPreferences();
677
678                 require_once ('modules/Versions/CheckVersions.php');
679                 $invalid_versions = get_invalid_versions();
680
681                 if (!empty ($invalid_versions)) {
682                         if (isset ($invalid_versions['Rebuild Relationships'])) {
683                                 unset ($invalid_versions['Rebuild Relationships']);
684
685                                 // flag for pickup in DisplayWarnings.php
686                                 $_SESSION['rebuild_relationships'] = true;
687                         }
688
689                         if (isset ($invalid_versions['Rebuild Extensions'])) {
690                                 unset ($invalid_versions['Rebuild Extensions']);
691
692                                 // flag for pickup in DisplayWarnings.php
693                                 $_SESSION['rebuild_extensions'] = true;
694                         }
695
696                         $_SESSION['invalid_versions'] = $invalid_versions;
697                 }
698                 if ($this->status != "Inactive")
699                         $this->authenticated = true;
700
701                 unset ($_SESSION['loginattempts']);
702                 return $this;
703         }
704
705         /**
706          * Generate a new hash from plaintext password
707          * @param string $password
708          */
709         public static function getPasswordHash($password)
710         {
711             if(!defined('CRYPT_MD5') || !constant('CRYPT_MD5')) {
712                 // does not support MD5 crypt - leave as is
713                 if(defined('CRYPT_EXT_DES') && constant('CRYPT_EXT_DES')) {
714                     return crypt(strtolower(md5($password)),
715                         "_.012".substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), -4));
716                 }
717                 // plain crypt cuts password to 8 chars, which is not enough
718                 // fall back to old md5
719                 return strtolower(md5($password));
720             }
721             return crypt(strtolower(md5($password)));
722         }
723
724         /**
725          * Check that password matches existing hash
726          * @param string $password Plaintext password
727          * @param string $user_hash DB hash
728          */
729         public static function checkPassword($password, $user_hash)
730         {
731             return self::checkPasswordMD5(md5($password), $user_hash);
732         }
733
734         /**
735          * Check that md5-encoded password matches existing hash
736          * @param string $password MD5-encoded password
737          * @param string $user_hash DB hash
738          * @return bool Match or not?
739          */
740         public static function checkPasswordMD5($password_md5, $user_hash)
741         {
742             if(empty($user_hash)) return false;
743             if($user_hash[0] != '$' && strlen($user_hash) == 32) {
744                 // Old way - just md5 password
745                 return strtolower($password_md5) == $user_hash;
746             }
747             return crypt(strtolower($password_md5), $user_hash) == $user_hash;
748         }
749
750         /**
751          * Find user with matching password
752          * @param string $name Username
753          * @param string $password MD5-encoded password
754          * @param string $where Limiting query
755          * @return the matching User of false if not found
756          */
757         public static function findUserPassword($name, $password, $where = '')
758         {
759             global $db;
760                 $name = $db->quote($name);
761                 $query = "SELECT * from users where user_name='$name'";
762                 if(!empty($where)) {
763                     $query .= " AND $where";
764                 }
765                 $result = $db->limitQuery($query,0,1,false);
766                 if(!empty($result)) {
767                     $row = $db->fetchByAssoc($result);
768                     if(self::checkPasswordMD5($password, $row['user_hash'])) {
769                         return $row;
770                     }
771                 }
772                 return false;
773         }
774
775         /**
776          * Sets new password and resets password expiration timers
777          * @param string $new_password
778          */
779         public function setNewPassword($new_password, $system_generated = '0')
780         {
781         $user_hash = self::getPasswordHash($new_password);
782         $this->setPreference('loginexpiration','0');
783             $this->setPreference('lockout','');
784                 $this->setPreference('loginfailed','0');
785                 $this->savePreferencesToDB();
786         //set new password
787         $now = TimeDate::getInstance()->nowDb();
788                 $query = "UPDATE $this->table_name SET user_hash='$user_hash', system_generated_password='$system_generated', pwd_last_changed='$now' where id='$this->id'";
789                 $this->db->query($query, true, "Error setting new password for $this->user_name: ");
790         $_SESSION['hasExpiredPassword'] = '0';
791         }
792
793         /**
794          * Verify that the current password is correct and write the new password to the DB.
795          *
796          * @param string $user name - Must be non null and at least 1 character.
797          * @param string $user_password - Must be non null and at least 1 character.
798          * @param string $new_password - Must be non null and at least 1 character.
799          * @return boolean - If passwords pass verification and query succeeds, return true, else return false.
800          */
801         function change_password($user_password, $new_password, $system_generated = '0')
802         {
803             global $mod_strings;
804                 global $current_user;
805                 $GLOBALS['log']->debug("Starting password change for $this->user_name");
806
807                 if (!isset ($new_password) || $new_password == "") {
808                         $this->error_string = $mod_strings['ERR_PASSWORD_CHANGE_FAILED_1'].$current_user->user_name.$mod_strings['ERR_PASSWORD_CHANGE_FAILED_2'];
809                         return false;
810                 }
811
812                 // Check new password against rules set by admin
813                 if (!$this->check_password_rules($new_password)) {
814                     $this->error_string = $mod_strings['ERR_PASSWORD_CHANGE_FAILED_1'].$current_user->user_name.$mod_strings['ERR_PASSWORD_CHANGE_FAILED_3'];
815                     return false;
816                 }
817
818                 if (!$current_user->isAdminForModule('Users')) {
819                         //check old password first
820                         $row = self::findUserPassword($this->user_name, md5($user_password));
821             if (empty($row)) {
822                                 $GLOBALS['log']->warn("Incorrect old password for ".$this->user_name."");
823                                 $this->error_string = $mod_strings['ERR_PASSWORD_INCORRECT_OLD_1'].$this->user_name.$mod_strings['ERR_PASSWORD_INCORRECT_OLD_2'];
824                                 return false;
825                         }
826                 }
827
828                 $this->setNewPassword($new_password, $system_generated);
829                 return true;
830         }
831
832         /**
833          * Check new password against rules set by admin
834          * @param string $password
835          * @return boolean
836          */
837         function check_password_rules($password) {
838             $length = mb_strlen($password);
839
840             // Min length
841             if(!empty($GLOBALS["sugar_config"]["passwordsetting"]["minpwdlength"]) && $GLOBALS["sugar_config"]["passwordsetting"]["minpwdlength"] > 0 && $length < $GLOBALS["sugar_config"]["passwordsetting"]["minpwdlength"]) {
842                 return false;
843             }
844
845             // Max length
846             if(!empty($GLOBALS['sugar_config']['passwordsetting']['maxpwdlength']) && $GLOBALS['sugar_config']['passwordsetting']['maxpwdlength'] > 0 && $length > $GLOBALS['sugar_config']['passwordsetting']['maxpwdlength']) {
847                 return false;
848             }
849
850             // One lower case
851             if(!empty($GLOBALS["sugar_config"]["passwordsetting"]["onelower"]) && !preg_match('/[a-z]+/', $password)){
852                 return false;
853             }
854
855             // One upper case
856             if(!empty($GLOBALS["sugar_config"]["passwordsetting"]["oneupper"]) && !preg_match('/[A-Z]+/', $password)){
857                 return false;
858             }
859
860             // One number
861             if(!empty($GLOBALS["sugar_config"]["passwordsetting"]["onenumber"]) && !preg_match('/[0-9]+/', $password)){
862                 return false;
863             }
864
865             // One special character
866             if(!empty($GLOBALS["sugar_config"]["passwordsetting"]["onespecial"]) && !preg_match('/[|}{~!@#$%^&*()_+=-]+/', $password)){
867                 return false;
868             }
869
870             // Custom regex
871             if(!empty($GLOBALS["sugar_config"]["passwordsetting"]["customregex"]) && !preg_match($GLOBALS["sugar_config"]["passwordsetting"]["customregex"], $password)){
872                 return false;
873             }
874
875             return true;
876         }
877
878         function is_authenticated() {
879                 return $this->authenticated;
880         }
881
882         function fill_in_additional_list_fields() {
883                 $this->fill_in_additional_detail_fields();
884         }
885
886         function fill_in_additional_detail_fields() {
887                 global $locale;
888
889                 $query = "SELECT u1.first_name, u1.last_name from users  u1, users  u2 where u1.id = u2.reports_to_id AND u2.id = '$this->id' and u1.deleted=0";
890                 $result = $this->db->query($query, true, "Error filling in additional detail fields");
891
892                 $row = $this->db->fetchByAssoc($result);
893
894                 if ($row != null) {
895                         $this->reports_to_name = stripslashes($row['first_name'].' '.$row['last_name']);
896                 } else {
897                         $this->reports_to_name = '';
898                 }
899
900                 $this->_create_proper_name_field();
901         }
902
903         public function retrieve_user_id(
904             $user_name
905             )
906         {
907             $userFocus = new User;
908             $userFocus->retrieve_by_string_fields(array('user_name'=>$user_name));
909             if ( empty($userFocus->id) )
910                 return false;
911
912         return $userFocus->id;
913         }
914
915         /**
916          * @return -- returns a list of all users in the system.
917          * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc..
918          * All Rights Reserved..
919          * Contributor(s): ______________________________________..
920          */
921         function verify_data($ieVerified=true) {
922                 global $mod_strings, $current_user;
923                 $verified = TRUE;
924
925                 if (!empty ($this->id)) {
926                         // Make sure the user doesn't report to themselves.
927                         $reports_to_self = 0;
928                         $check_user = $this->reports_to_id;
929                         $already_seen_list = array ();
930                         while (!empty ($check_user)) {
931                                 if (isset ($already_seen_list[$check_user])) {
932                                         // This user doesn't actually report to themselves
933                                         // But someone above them does.
934                                         $reports_to_self = 1;
935                                         break;
936                                 }
937                                 if ($check_user == $this->id) {
938                                         $reports_to_self = 1;
939                                         break;
940                                 }
941                                 $already_seen_list[$check_user] = 1;
942                                 $query = "SELECT reports_to_id FROM users WHERE id='".$this->db->quote($check_user)."'";
943                                 $result = $this->db->query($query, true, "Error checking for reporting-loop");
944                                 $row = $this->db->fetchByAssoc($result);
945                                 echo ("fetched: ".$row['reports_to_id']." from ".$check_user."<br>");
946                                 $check_user = $row['reports_to_id'];
947                         }
948
949                         if ($reports_to_self == 1) {
950                                 $this->error_string .= $mod_strings['ERR_REPORT_LOOP'];
951                                 $verified = FALSE;
952                         }
953                 }
954
955                 $query = "SELECT user_name from users where user_name='$this->user_name' AND deleted=0";
956                 if(!empty($this->id))$query .=  " AND id<>'$this->id'";
957                 $result = $this->db->query($query, true, "Error selecting possible duplicate users: ");
958                 $dup_users = $this->db->fetchByAssoc($result);
959
960                 if (!empty($dup_users)) {
961                         $this->error_string .= $mod_strings['ERR_USER_NAME_EXISTS_1'].$this->user_name.$mod_strings['ERR_USER_NAME_EXISTS_2'];
962                         $verified = FALSE;
963                 }
964
965                 if (is_admin($current_user)) {
966                     $remaining_admins = $this->db->getOne("SELECT COUNT(*) as c from users where is_admin = 1 AND deleted=0");
967
968                         if (($remaining_admins <= 1) && ($this->is_admin != '1') && ($this->id == $current_user->id)) {
969                                 $GLOBALS['log']->debug("Number of remaining administrator accounts: {$remaining_admins}");
970                                 $this->error_string .= $mod_strings['ERR_LAST_ADMIN_1'].$this->user_name.$mod_strings['ERR_LAST_ADMIN_2'];
971                                 $verified = FALSE;
972                         }
973                 }
974                 ///////////////////////////////////////////////////////////////////////
975                 ////    InboundEmail verification failure
976                 if(!$ieVerified) {
977                         $verified = false;
978                         $this->error_string .= '<br />'.$mod_strings['ERR_EMAIL_NO_OPTS'];
979                 }
980
981                 return $verified;
982         }
983
984         function get_list_view_data() {
985
986                 global $current_user, $mod_strings;
987         // Bug #48555 Not User Name Format of User's locale.
988         $this->_create_proper_name_field();
989
990                 $user_fields = $this->get_list_view_array();
991                 if ($this->is_admin)
992                         $user_fields['IS_ADMIN_IMAGE'] = SugarThemeRegistry::current()->getImage('check_inline', '',null,null,'.gif',$mod_strings['LBL_CHECKMARK']);
993                 elseif (!$this->is_admin) $user_fields['IS_ADMIN'] = '';
994                 if ($this->is_group)
995                         $user_fields['IS_GROUP_IMAGE'] = SugarThemeRegistry::current()->getImage('check_inline', '',null,null,'.gif',$mod_strings['LBL_CHECKMARK']);
996                 else
997                         $user_fields['IS_GROUP_IMAGE'] = '';
998
999
1000         if ($this->is_admin) {
1001                         $user_fields['IS_ADMIN_IMAGE'] = SugarThemeRegistry::current()->getImage('check_inline', '',null,null,'.gif',translate('LBL_CHECKMARK', 'Users'));
1002         } elseif (!$this->is_admin) {
1003               $user_fields['IS_ADMIN'] = '';
1004         }
1005
1006         if ($this->is_group) {
1007                 $user_fields['IS_GROUP_IMAGE'] = SugarThemeRegistry::current()->getImage('check_inline', '',null,null,'.gif',translate('LBL_CHECKMARK', 'Users'));
1008         } else {
1009             $user_fields['NAME'] = empty ($this->name) ? '' : $this->name;
1010         }
1011
1012                 $user_fields['REPORTS_TO_NAME'] = $this->reports_to_name;
1013
1014                 $user_fields['EMAIL1'] = $this->emailAddress->getPrimaryAddress($this);
1015
1016                 return $user_fields;
1017         }
1018
1019         function list_view_parse_additional_sections(& $list_form, $xTemplateSection) {
1020                 return $list_form;
1021         }
1022
1023
1024
1025
1026     /**
1027      * getAllUsers
1028      *
1029      * Returns all active and inactive users
1030      * @return Array of all users in the system
1031      */
1032
1033     public static function getAllUsers()
1034     {
1035         $active_users = get_user_array(FALSE);
1036         $inactive_users = get_user_array(FALSE, "Inactive");
1037         $result = $active_users + $inactive_users;
1038         asort($result);
1039         return $result;
1040     }
1041
1042         function create_export_query($order_by, $where) {
1043                 include('modules/Users/field_arrays.php');
1044
1045                 $cols = '';
1046                 foreach($fields_array['User']['export_fields'] as $field) {
1047                         $cols .= (empty($cols)) ? '' : ', ';
1048                         $cols .= $field;
1049                 }
1050
1051                 $query = "SELECT {$cols} FROM users ";
1052
1053                 $where_auto = " users.deleted = 0";
1054
1055                 if ($where != "")
1056                         $query .= " WHERE $where AND ".$where_auto;
1057                 else
1058                         $query .= " WHERE ".$where_auto;
1059
1060                 // admin for module user is not be able to export a super-admin
1061                 global $current_user;
1062                 if(!$current_user->is_admin){
1063                         $query .= " AND users.is_admin=0";
1064                 }
1065
1066                 if ($order_by != "")
1067                         $query .= " ORDER BY $order_by";
1068                 else
1069                         $query .= " ORDER BY users.user_name";
1070
1071                 return $query;
1072         }
1073
1074         /** Returns a list of the associated users
1075          * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc..
1076          * All Rights Reserved..
1077          * Contributor(s): ______________________________________..
1078         */
1079         function get_meetings() {
1080                 // First, get the list of IDs.
1081                 $query = "SELECT meeting_id as id from meetings_users where user_id='$this->id' AND deleted=0";
1082                 return $this->build_related_list($query, new Meeting());
1083         }
1084         function get_calls() {
1085                 // First, get the list of IDs.
1086                 $query = "SELECT call_id as id from calls_users where user_id='$this->id' AND deleted=0";
1087                 return $this->build_related_list($query, new Call());
1088         }
1089
1090         /**
1091          * generates Javascript to display I-E mail counts, both personal and group
1092          */
1093         function displayEmailCounts() {
1094                 global $theme;
1095                 $new = translate('LBL_NEW', 'Emails');
1096                 $default = 'index.php?module=Emails&action=ListView&assigned_user_id='.$this->id;
1097                 $count = '';
1098                 $verts = array('Love', 'Links', 'Pipeline', 'RipCurl', 'SugarLite');
1099
1100                 if($this->hasPersonalEmail()) {
1101                         $r = $this->db->query('SELECT count(*) AS c FROM emails WHERE deleted=0 AND assigned_user_id = \''.$this->id.'\' AND type = \'inbound\' AND status = \'unread\'');
1102                         $a = $this->db->fetchByAssoc($r);
1103                         if(in_array($theme, $verts)) {
1104                                 $count .= '<br />';
1105                         } else {
1106                                 $count .= '&nbsp;&nbsp;&nbsp;&nbsp;';
1107                         }
1108                         $count .= '<a href='.$default.'&type=inbound>'.translate('LBL_LIST_TITLE_MY_INBOX', 'Emails').': ('.$a['c'].' '.$new.')</a>';
1109
1110                         if(!in_array($theme, $verts)) {
1111                                 $count .= ' - ';
1112                         }
1113                 }
1114
1115                 $r = $this->db->query('SELECT id FROM users WHERE users.is_group = 1 AND deleted = 0');
1116                 $groupIds = '';
1117                 $groupNew = '';
1118                 while($a = $this->db->fetchByAssoc($r)) {
1119                         if($groupIds != '') {$groupIds .= ', ';}
1120                         $groupIds .= "'".$a['id']."'";
1121                 }
1122
1123                 $total = 0;
1124                 if(strlen($groupIds) > 0) {
1125                         $groupQuery = 'SELECT count(*) AS c FROM emails ';
1126                         $groupQuery .= ' WHERE emails.deleted=0 AND emails.assigned_user_id IN ('.$groupIds.') AND emails.type = \'inbound\' AND emails.status = \'unread\'';
1127                         $r = $this->db->query($groupQuery);
1128                         if(is_resource($r)) {
1129                                 $a = $this->db->fetchByAssoc($r);
1130                                 if($a['c'] > 0) {
1131                                         $total = $a['c'];
1132                                 }
1133                         }
1134                 }
1135                 if(in_array($theme, $verts)) $count .= '<br />';
1136                 if(empty($count)) $count .= '&nbsp;&nbsp;&nbsp;&nbsp;';
1137                 $count .= '<a href=index.php?module=Emails&action=ListViewGroup>'.translate('LBL_LIST_TITLE_GROUP_INBOX', 'Emails').': ('.$total.' '.$new.')</a>';
1138
1139                 $out  = '<script type="text/javascript" language="Javascript">';
1140                 $out .= 'var welcome = document.getElementById("welcome");';
1141                 $out .= 'var welcomeContent = welcome.innerHTML;';
1142                 $out .= 'welcome.innerHTML = welcomeContent + "'.$count.'";';
1143                 $out .= '</script>';
1144
1145                 echo $out;
1146         }
1147
1148         function getPreferredEmail() {
1149                 $ret = array ();
1150                 $nameEmail = $this->getUsersNameAndEmail();
1151                 $prefAddr = $nameEmail['email'];
1152                 $fullName = $nameEmail['name'];
1153                 if (empty ($prefAddr)) {
1154                         $nameEmail = $this->getSystemDefaultNameAndEmail();
1155                         $prefAddr = $nameEmail['email'];
1156                         $fullName = $nameEmail['name'];
1157                 } // if
1158                 $fullName = from_html($fullName);
1159                 $ret['name'] = $fullName;
1160                 $ret['email'] = $prefAddr;
1161                 return $ret;
1162         }
1163
1164         function getUsersNameAndEmail()
1165         {
1166             // Bug #48555 Not User Name Format of User's locale.
1167             $this->_create_proper_name_field();
1168
1169                 $prefAddr = $this->emailAddress->getPrimaryAddress($this);
1170
1171                 if (empty ($prefAddr)) {
1172                         $prefAddr = $this->emailAddress->getReplyToAddress($this);
1173                 }
1174                 return array('email' => $prefAddr , 'name' => $this->name);
1175
1176         } // fn
1177
1178         function getSystemDefaultNameAndEmail() {
1179
1180                 $email = new Email();
1181                 $return = $email->getSystemDefaultEmail();
1182                 $prefAddr = $return['email'];
1183                 $fullName = $return['name'];
1184                 return array('email' => $prefAddr , 'name' => $fullName);
1185         } // fn
1186
1187         /**
1188          * sets User email default in config.php if not already set by install - i.
1189          * e., upgrades
1190          */
1191         function setDefaultsInConfig() {
1192                 global $sugar_config;
1193                 $sugar_config['email_default_client'] = 'sugar';
1194                 $sugar_config['email_default_editor'] = 'html';
1195                 ksort($sugar_config);
1196                 write_array_to_file('sugar_config', $sugar_config, 'config.php');
1197                 return $sugar_config;
1198         }
1199
1200     /**
1201      * returns User's email address based on descending order of preferences
1202      *
1203      * @param string id GUID of target user if needed
1204      * @return array Assoc array for an email and name
1205      */
1206     function getEmailInfo($id='') {
1207         $user = $this;
1208         if(!empty($id)) {
1209             $user = new User();
1210             $user->retrieve($id);
1211         }
1212
1213         // from name
1214         $fromName = $user->getPreference('mail_fromname');
1215         if(empty($fromName)) {
1216                 // cn: bug 8586 - localized name format
1217             $fromName = $user->full_name;
1218         }
1219
1220         // from address
1221         $fromaddr = $user->getPreference('mail_fromaddress');
1222         if(empty($fromaddr)) {
1223             if(!empty($user->email1) && isset($user->email1)) {
1224                 $fromaddr = $user->email1;
1225             } elseif(!empty($user->email2) && isset($user->email2)) {
1226                 $fromaddr = $user->email2;
1227             } else {
1228                 $r = $user->db->query("SELECT value FROM config WHERE name = 'fromaddress'");
1229                 $a = $user->db->fetchByAssoc($r);
1230                 $fromddr = $a['value'];
1231             }
1232         }
1233
1234         $ret['name'] = $fromName;
1235         $ret['email'] = $fromaddr;
1236
1237         return $ret;
1238     }
1239
1240         /**
1241          * returns opening <a href=xxxx for a contact, account, etc
1242          * cascades from User set preference to System-wide default
1243          * @return string       link
1244          * @param attribute the email addy
1245          * @param focus the parent bean
1246          * @param contact_id
1247          * @param return_module
1248          * @param return_action
1249          * @param return_id
1250          * @param class
1251          */
1252         function getEmailLink2($emailAddress, &$focus, $contact_id='', $ret_module='', $ret_action='DetailView', $ret_id='', $class='') {
1253                 $emailLink = '';
1254                 global $sugar_config;
1255
1256                 if(!isset($sugar_config['email_default_client'])) {
1257                         $this->setDefaultsInConfig();
1258                 }
1259
1260                 $userPref = $this->getPreference('email_link_type');
1261                 $defaultPref = $sugar_config['email_default_client'];
1262                 if($userPref != '') {
1263                         $client = $userPref;
1264                 } else {
1265                         $client = $defaultPref;
1266                 }
1267
1268                 if($client == 'sugar') {
1269                         $email = '';
1270                         $to_addrs_ids = '';
1271                         $to_addrs_names = '';
1272                         $to_addrs_emails = '';
1273
1274                         $fullName = !empty($focus->name) ? $focus->name : '';
1275
1276                         if(empty($ret_module)) $ret_module = $focus->module_dir;
1277                         if(empty($ret_id)) $ret_id = $focus->id;
1278                         if($focus->object_name == 'Contact') {
1279                                 $contact_id = $focus->id;
1280                                 $to_addrs_ids = $focus->id;
1281                                 // Bug #48555 Not User Name Format of User's locale.
1282                                 $focus->_create_proper_name_field();
1283                             $fullName = $focus->name;
1284                             $to_addrs_names = $fullName;
1285                                 $to_addrs_emails = $focus->email1;
1286                         }
1287
1288                         $emailLinkUrl = 'contact_id='.$contact_id.
1289                                 '&parent_type='.$focus->module_dir.
1290                                 '&parent_id='.$focus->id.
1291                                 '&parent_name='.urlencode($fullName).
1292                                 '&to_addrs_ids='.$to_addrs_ids.
1293                                 '&to_addrs_names='.urlencode($to_addrs_names).
1294                                 '&to_addrs_emails='.urlencode($to_addrs_emails).
1295                                 '&to_email_addrs='.urlencode($fullName . '&nbsp;&lt;' . $emailAddress . '&gt;').
1296                                 '&return_module='.$ret_module.
1297                                 '&return_action='.$ret_action.
1298                                 '&return_id='.$ret_id;
1299
1300                 //Generate the compose package for the quick create options.
1301                 //$json = getJSONobj();
1302                 //$composeOptionsLink = $json->encode( array('composeOptionsLink' => $emailLinkUrl,'id' => $focus->id) );
1303                         require_once('modules/Emails/EmailUI.php');
1304             $eUi = new EmailUI();
1305             $j_quickComposeOptions = $eUi->generateComposePackageForQuickCreateFromComposeUrl($emailLinkUrl, true);
1306
1307                 $emailLink = "<a href='javascript:void(0);' onclick='SUGAR.quickCompose.init($j_quickComposeOptions);' class='$class'>";
1308
1309                 } else {
1310                         // straight mailto:
1311                         $emailLink = '<a href="mailto:'.$emailAddress.'" class="'.$class.'">';
1312                 }
1313
1314                 return $emailLink;
1315         }
1316
1317         /**
1318          * returns opening <a href=xxxx for a contact, account, etc
1319          * cascades from User set preference to System-wide default
1320          * @return string       link
1321          * @param attribute the email addy
1322          * @param focus the parent bean
1323          * @param contact_id
1324          * @param return_module
1325          * @param return_action
1326          * @param return_id
1327          * @param class
1328          */
1329         function getEmailLink($attribute, &$focus, $contact_id='', $ret_module='', $ret_action='DetailView', $ret_id='', $class='') {
1330             $emailLink = '';
1331                 global $sugar_config;
1332
1333                 if(!isset($sugar_config['email_default_client'])) {
1334                         $this->setDefaultsInConfig();
1335                 }
1336
1337                 $userPref = $this->getPreference('email_link_type');
1338                 $defaultPref = $sugar_config['email_default_client'];
1339                 if($userPref != '') {
1340                         $client = $userPref;
1341                 } else {
1342                         $client = $defaultPref;
1343                 }
1344
1345                 if($client == 'sugar') {
1346                         $email = '';
1347                         $to_addrs_ids = '';
1348                         $to_addrs_names = '';
1349                         $to_addrs_emails = '';
1350
1351             $fullName = !empty($focus->name) ? $focus->name : '';
1352
1353                         if(!empty($focus->$attribute)) {
1354                                 $email = $focus->$attribute;
1355                         }
1356
1357
1358                         if(empty($ret_module)) $ret_module = $focus->module_dir;
1359                         if(empty($ret_id)) $ret_id = $focus->id;
1360                         if($focus->object_name == 'Contact') {
1361                                 // Bug #48555 Not User Name Format of User's locale.
1362                                 $focus->_create_proper_name_field();
1363                             $fullName = $focus->name;
1364                             $contact_id = $focus->id;
1365                                 $to_addrs_ids = $focus->id;
1366                                 $to_addrs_names = $fullName;
1367                                 $to_addrs_emails = $focus->email1;
1368                         }
1369
1370                         $emailLinkUrl = 'contact_id='.$contact_id.
1371                                 '&parent_type='.$focus->module_dir.
1372                                 '&parent_id='.$focus->id.
1373                                 '&parent_name='.urlencode($fullName).
1374                                 '&to_addrs_ids='.$to_addrs_ids.
1375                                 '&to_addrs_names='.urlencode($to_addrs_names).
1376                                 '&to_addrs_emails='.urlencode($to_addrs_emails).
1377                                 '&to_email_addrs='.urlencode($fullName . '&nbsp;&lt;' . $email . '&gt;').
1378                                 '&return_module='.$ret_module.
1379                                 '&return_action='.$ret_action.
1380                                 '&return_id='.$ret_id;
1381
1382                         //Generate the compose package for the quick create options.
1383                 require_once('modules/Emails/EmailUI.php');
1384             $eUi = new EmailUI();
1385             $j_quickComposeOptions = $eUi->generateComposePackageForQuickCreateFromComposeUrl($emailLinkUrl, true);
1386                 $emailLink = "<a href='javascript:void(0);' onclick='SUGAR.quickCompose.init($j_quickComposeOptions);' class='$class'>";
1387
1388                 } else {
1389                         // straight mailto:
1390                         $emailLink = '<a href="mailto:'.$focus->$attribute.'" class="'.$class.'">';
1391                 }
1392
1393                 return $emailLink;
1394         }
1395
1396
1397         /**
1398          * gets a human-readable explanation of the format macro
1399          * @return string Human readable name format
1400          */
1401         function getLocaleFormatDesc() {
1402                 global $locale;
1403                 global $mod_strings;
1404                 global $app_strings;
1405
1406                 $format['f'] = $mod_strings['LBL_LOCALE_DESC_FIRST'];
1407                 $format['l'] = $mod_strings['LBL_LOCALE_DESC_LAST'];
1408                 $format['s'] = $mod_strings['LBL_LOCALE_DESC_SALUTATION'];
1409                 $format['t'] = $mod_strings['LBL_LOCALE_DESC_TITLE'];
1410
1411                 $name['f'] = $app_strings['LBL_LOCALE_NAME_EXAMPLE_FIRST'];
1412                 $name['l'] = $app_strings['LBL_LOCALE_NAME_EXAMPLE_LAST'];
1413                 $name['s'] = $app_strings['LBL_LOCALE_NAME_EXAMPLE_SALUTATION'];
1414                 $name['t'] = $app_strings['LBL_LOCALE_NAME_EXAMPLE_TITLE'];
1415
1416                 $macro = $locale->getLocaleFormatMacro();
1417
1418                 $ret1 = '';
1419                 $ret2 = '';
1420                 for($i=0; $i<strlen($macro); $i++) {
1421                         if(array_key_exists($macro{$i}, $format)) {
1422                                 $ret1 .= "<i>".$format[$macro{$i}]."</i>";
1423                                 $ret2 .= "<i>".$name[$macro{$i}]."</i>";
1424                         } else {
1425                                 $ret1 .= $macro{$i};
1426                                 $ret2 .= $macro{$i};
1427                         }
1428                 }
1429                 return $ret1."<br />".$ret2;
1430         }
1431
1432
1433     /*
1434      *
1435      * Here are the multi level admin access check functions.
1436      *
1437      */
1438     /**
1439      * Helper function to remap some modules around ACL wise
1440      *
1441      * @return string
1442      */
1443     protected function _fixupModuleForACL($module) {
1444         if($module=='ContractTypes') {
1445             $module = 'Contracts';
1446         }
1447         if(preg_match('/Product[a-zA-Z]*/',$module)) {
1448             $module = 'Products';
1449         }
1450
1451         return $module;
1452     }
1453     /**
1454      * Helper function that enumerates the list of modules and checks if they are an admin/dev.
1455      * The code was just too similar to copy and paste.
1456      *
1457      * @return array
1458      */
1459     protected function _getModulesForACL($type='dev'){
1460         $isDev = $type=='dev';
1461         $isAdmin = $type=='admin';
1462
1463         global $beanList;
1464         $myModules = array();
1465
1466         if (!is_array($beanList) ) {
1467             return $myModules;
1468         }
1469
1470         // These modules don't take kindly to the studio trying to play about with them.
1471         static $ignoredModuleList = array('iFrames','Feeds','Home','Dashboard','Calendar','Activities','Reports');
1472
1473
1474         $actions = ACLAction::getUserActions($this->id);
1475
1476         foreach ($beanList as $module=>$val) {
1477             // Remap the module name
1478             $module = $this->_fixupModuleForACL($module);
1479             if (in_array($module,$myModules)) {
1480                 // Already have the module in the list
1481                 continue;
1482             }
1483             if (in_array($module,$ignoredModuleList)) {
1484                 // You can't develop on these modules.
1485                 continue;
1486             }
1487
1488             $focus = SugarModule::get($module)->loadBean();
1489             if ( $focus instanceOf SugarBean ) {
1490                 $key = $focus->acltype;
1491             } else {
1492                 $key = 'module';
1493             }
1494
1495             if (($this->isAdmin() && isset($actions[$module][$key]))
1496                 ) {
1497                 $myModules[] = $module;
1498             }
1499         }
1500
1501         return $myModules;
1502     }
1503     /**
1504      * Is this user a system wide admin
1505      *
1506      * @return bool
1507      */
1508     public function isAdmin() {
1509         if(isset($this->is_admin)
1510            &&($this->is_admin == '1' || $this->is_admin === 'on')){
1511             return true;
1512         }
1513         return false;
1514     }
1515     /**
1516      * Is this user a developer for any module
1517      *
1518      * @return bool
1519      */
1520     public function isDeveloperForAnyModule() {
1521         if ($this->isAdmin()) {
1522             return true;
1523         }
1524         return false;
1525     }
1526     /**
1527      * List the modules a user has developer access to
1528      *
1529      * @return array
1530      */
1531     public function getDeveloperModules() {
1532         static $developerModules;
1533         if (!isset($_SESSION[$this->user_name.'_get_developer_modules_for_user']) ) {
1534             $_SESSION[$this->user_name.'_get_developer_modules_for_user'] = $this->_getModulesForACL('dev');
1535         }
1536
1537         return $_SESSION[$this->user_name.'_get_developer_modules_for_user'];
1538     }
1539     /**
1540      * Is this user a developer for the specified module
1541      *
1542      * @return bool
1543      */
1544     public function isDeveloperForModule($module) {
1545         if ($this->isAdmin()) {
1546             return true;
1547         }
1548
1549         $devModules = $this->getDeveloperModules();
1550
1551         $module = $this->_fixupModuleForACL($module);
1552
1553         if (in_array($module,$devModules) ) {
1554             return true;
1555         }
1556
1557         return false;
1558     }
1559     /**
1560      * List the modules a user has admin access to
1561      *
1562      * @return array
1563      */
1564     public function getAdminModules() {
1565         if (!isset($_SESSION[$this->user_name.'_get_admin_modules_for_user']) ) {
1566             $_SESSION[$this->user_name.'_get_admin_modules_for_user'] = $this->_getModulesForACL('admin');
1567         }
1568
1569         return $_SESSION[$this->user_name.'_get_admin_modules_for_user'];
1570     }
1571     /**
1572      * Is this user an admin for the specified module
1573      *
1574      * @return bool
1575      */
1576     public function isAdminForModule($module) {
1577         if ($this->isAdmin()) {
1578             return true;
1579         }
1580
1581         $adminModules = $this->getAdminModules();
1582
1583         $module = $this->_fixupModuleForACL($module);
1584
1585         if (in_array($module,$adminModules) ) {
1586             return true;
1587         }
1588
1589         return false;
1590     }
1591         /**
1592          * Whether or not based on the user's locale if we should show the last name first.
1593          *
1594          * @return bool
1595          */
1596         public function showLastNameFirst(){
1597                 global $locale;
1598         $localeFormat = $locale->getLocaleFormatMacro($this);
1599                 if ( strpos($localeFormat,'l') > strpos($localeFormat,'f') ) {
1600                     return false;
1601         }else {
1602                 return true;
1603         }
1604         }
1605
1606    function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false)
1607    {    //call parent method, specifying for array to be returned
1608         $ret_array = parent::create_new_list_query($order_by, $where,$filter,$params, $show_deleted,$join_type, true,$parentbean, $singleSelect);
1609
1610         //if this is being called from webservices, then run additional code
1611         if(!empty($GLOBALS['soap_server_object'])){
1612
1613                 //if this is a single select, then secondary queries are being run that may result in duplicate rows being returned through the
1614                 //left joins with meetings/tasks/call.  We need to change the left joins to include a null check (bug 40250)
1615                 if($singleSelect)
1616                 {
1617                         //retrieve the 'from' string and make lowercase for easier manipulation
1618                         $left_str = strtolower($ret_array['from']);
1619                         $lefts = explode('left join', $left_str);
1620                         $new_left_str = '';
1621
1622                         //explode on the left joins and process each one
1623                         foreach($lefts as $ljVal){
1624                                 //grab the join alias
1625                                 $onPos = strpos( $ljVal, ' on');
1626                                 if($onPos === false){
1627                                         $new_left_str .=' '.$ljVal.' ';
1628                                         continue;
1629                                 }
1630                                 $spacePos = strrpos(substr($ljVal, 0, $onPos),' ');
1631                                 $alias = substr($ljVal,$spacePos,$onPos-$spacePos);
1632
1633                                 //add null check to end of the Join statement
1634                         // Bug #46390 to use id_c field instead of id field for custom tables
1635                         if(substr($alias, -5) != '_cstm')
1636                         {
1637                             $ljVal ='  LEFT JOIN '.$ljVal.' and '.$alias.'.id is null ';
1638                         }
1639                         else
1640                         {
1641                             $ljVal ='  LEFT JOIN '.$ljVal.' and '.$alias.'.id_c is null ';
1642                         }
1643
1644                                 //add statement into new string
1645                                 $new_left_str .= $ljVal;
1646                          }
1647                          //replace the old string with the new one
1648                          $ret_array['from'] = $new_left_str;
1649                 }
1650         }
1651
1652                 //return array or query string
1653                 if($return_array)
1654         {
1655                 return $ret_array;
1656         }
1657
1658         return  $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
1659
1660
1661
1662    }
1663
1664     /**
1665      * Get user first day of week.
1666      *
1667      * @param [User] $user user object, current user if not specified
1668      * @return int : 0 = Sunday, 1 = Monday, etc...
1669      */
1670     public function get_first_day_of_week()
1671     {
1672         $fdow = $this->getPreference('fdow');
1673         if (empty($fdow))
1674         {
1675             $fdow = 0;
1676         }
1677
1678         return $fdow;
1679     }
1680
1681     /**
1682      * Method for password generation
1683      *
1684      * @static
1685      * @return string password
1686      */
1687     public static function generatePassword()
1688     {
1689         $res = $GLOBALS['sugar_config']['passwordsetting'];
1690         $charBKT = '';
1691         //chars to select from
1692         $LOWERCASE = "abcdefghijklmnpqrstuvwxyz";
1693         $NUMBER = "0123456789";
1694         $UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1695         $SPECIAL = '~!@#$%^&*()_+=-{}|';
1696         $condition = 0;
1697         $charBKT .= $UPPERCASE . $LOWERCASE . $NUMBER;
1698         $password = "";
1699             $length = '6';
1700
1701         // Create random characters for the ones that doesnt have requirements
1702         for ($i=0; $i < $length - $condition; $i ++)  // loop and create password
1703         {
1704             $password = $password . substr ($charBKT, rand() % strlen($charBKT), 1);
1705         }
1706
1707         return $password;
1708     }
1709
1710     /**
1711      * Send new password or link to user
1712      *
1713      * @param string $templateId Id of email template
1714      * @param array $additionalData additional params: link, url, password
1715      * @return array status: true|false, message: error message, if status = false and message = '' it means that send method has returned false
1716      */
1717     public function sendEmailForPassword($templateId, array $additionalData = array())
1718     {
1719         global $sugar_config, $current_user;
1720         $mod_strings = return_module_language('', 'Users');
1721         $result = array(
1722             'status' => false,
1723             'message' => ''
1724         );
1725
1726         $emailTemp = new EmailTemplate();
1727         $emailTemp->disable_row_level_security = true;
1728         if ($emailTemp->retrieve($templateId) == '')
1729         {
1730             $result['message'] = $mod_strings['LBL_EMAIL_TEMPLATE_MISSING'];
1731             return $result;
1732         }
1733
1734         //replace instance variables in email templates
1735         $htmlBody = $emailTemp->body_html;
1736         $body = $emailTemp->body;
1737         if (isset($additionalData['link']) && $additionalData['link'] == true)
1738         {
1739             $htmlBody = str_replace('$contact_user_link_guid', $additionalData['url'], $htmlBody);
1740             $body = str_replace('$contact_user_link_guid', $additionalData['url'], $body);
1741         }
1742         else
1743         {
1744             $htmlBody = str_replace('$contact_user_user_hash', $additionalData['password'], $htmlBody);
1745             $body = str_replace('$contact_user_user_hash', $additionalData['password'], $body);
1746         }
1747         // Bug 36833 - Add replacing of special value $instance_url
1748         $htmlBody = str_replace('$config_site_url', $sugar_config['site_url'], $htmlBody);
1749         $body = str_replace('$config_site_url', $sugar_config['site_url'], $body);
1750
1751         $htmlBody = str_replace('$contact_user_user_name', $this->user_name, $htmlBody);
1752         $htmlBody = str_replace('$contact_user_pwd_last_changed', TimeDate::getInstance()->nowDb(), $htmlBody);
1753         $body = str_replace('$contact_user_user_name', $this->user_name, $body);
1754         $body = str_replace('$contact_user_pwd_last_changed', TimeDate::getInstance()->nowDb(), $body);
1755         $emailTemp->body_html = $htmlBody;
1756         $emailTemp->body = $body;
1757
1758         $itemail = $this->emailAddress->getPrimaryAddress($this);
1759         //retrieve IT Admin Email
1760         //_ppd( $emailTemp->body_html);
1761         //retrieve email defaults
1762         $emailObj = new Email();
1763         $defaults = $emailObj->getSystemDefaultEmail();
1764         require_once('include/SugarPHPMailer.php');
1765         $mail = new SugarPHPMailer();
1766         $mail->setMailerForSystem();
1767         //$mail->IsHTML(true);
1768         $mail->From = $defaults['email'];
1769         $mail->FromName = $defaults['name'];
1770         $mail->ClearAllRecipients();
1771         $mail->ClearReplyTos();
1772         $mail->Subject = from_html($emailTemp->subject);
1773         if ($emailTemp->text_only != 1)
1774         {
1775             $mail->IsHTML(true);
1776             $mail->Body = from_html($emailTemp->body_html);
1777             $mail->AltBody = from_html($emailTemp->body);
1778         }
1779         else
1780         {
1781             $mail->Body_html = from_html($emailTemp->body_html);
1782             $mail->Body = from_html($emailTemp->body);
1783         }
1784         if ($mail->Body == '' && $current_user->is_admin)
1785         {
1786             global $app_strings;
1787             $result['message'] = $app_strings['LBL_EMAIL_TEMPLATE_EDIT_PLAIN_TEXT'];
1788             return $result;
1789         }
1790         if ($mail->Mailer == 'smtp' && $mail->Host =='' && $current_user->is_admin)
1791         {
1792             $result['message'] = $mod_strings['ERR_SERVER_SMTP_EMPTY'];
1793             return $result;
1794         }
1795
1796         $mail->prepForOutbound();
1797         $hasRecipients = false;
1798
1799         if (!empty($itemail))
1800         {
1801             if ($hasRecipients)
1802             {
1803                 $mail->AddBCC($itemail);
1804             }
1805             else
1806             {
1807                 $mail->AddAddress($itemail);
1808             }
1809             $hasRecipients = true;
1810         }
1811         if ($hasRecipients)
1812         {
1813             $result['status'] = @$mail->Send();
1814         }
1815
1816         if ($result['status'] == true)
1817         {
1818             $emailObj->team_id = 1;
1819             $emailObj->to_addrs = '';
1820             $emailObj->type = 'archived';
1821             $emailObj->deleted = '0';
1822             $emailObj->name = $mail->Subject ;
1823             $emailObj->description = $mail->Body;
1824             $emailObj->description_html = null;
1825             $emailObj->from_addr = $mail->From;
1826             $emailObj->parent_type = 'User';
1827             $emailObj->date_sent = TimeDate::getInstance()->nowDb();
1828             $emailObj->modified_user_id = '1';
1829             $emailObj->created_by = '1';
1830             $emailObj->status = 'sent';
1831             $emailObj->save();
1832             if (!isset($additionalData['link']) || $additionalData['link'] == false)
1833             {
1834                 $this->setNewPassword($additionalData['password'], '1');
1835             }
1836         }
1837
1838         return $result;
1839     }
1840
1841     // Bug #48014 Must to send password to imported user if this action is required
1842     function afterImportSave()
1843     {
1844         if(
1845             $this->user_hash == false
1846             && !$this->is_group
1847             && !$this->portal_only
1848             && isset($GLOBALS['sugar_config']['passwordsetting']['SystemGeneratedPasswordON'])
1849             && $GLOBALS['sugar_config']['passwordsetting']['SystemGeneratedPasswordON']
1850         )
1851         {
1852             $backUpPost = $_POST;
1853             $_POST = array(
1854                 'userId' => $this->id
1855             );
1856             ob_start();
1857             require('modules/Users/GeneratePassword.php');
1858             $result = ob_get_clean();
1859             $_POST = $backUpPost;
1860             return $result == true;
1861         }
1862     }
1863 }