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