Last Login Users data

Here are the instructions to create a history of logins and logouts for your users. With this solution you get a list of all the login/logout actions by each user, not just the most recent login/logout actions by each user.

Some similarities in the coding part and if you wish, you can do both (see the coding above) but these instructions assume you are just going to create the historical records.

This creates a subpanel in each user’s page so they can see their own login/logout actions and creates a listview on the new module page accessible by the Administrator (and anyone else to whom you give access) that shows the login/logout history for all users. It also prevents users from Creating,Updating or Deleting entries; all entries are system generated based on user login/logout actions.

Steps to create this solution:

  1. Create a new module to hold the login/logout data
  2. Relate the new module to the Users’ module
  3. Create the script that auto-populates the login/logout data based on user actions
  1. Create a new module to hold the login/logout data
    As an administrator, using SuiteCRM->Admin->Developer Tools->Module Builder
		New Package
			Package Name: useractivitylog
			Author: 
			Prefix: rcual
			Description: Record user login and logout activity
			ReadMe: This package requires custom script to populate the new table and relationships
			Save
			New Module
				Module Name: useractivity
				Label: User Activity
				Importing: unchecked
				Navigation: Checked
				Type: Basic
				Save
			Customize new module -> expand useractivylog and useractivity on left menu
				Add Fields
					Type: Text
						Field Name: useractivitytype
						Display Label: Activity Type
						Inline Edit: No
						Importable: No
						Save
					Type: datetime
						Field Name: useractivitytime
						Display Label: Activity Type
						Inline Edit: No
						Importable: No
						Save
			Edit the Available Subpanels -> Default (for some reason you have to do this 2x and save 2x)
				Move Name from Default column to Hidden Column
				Move Remove from Default column to Hidden Column
				Move Edit from Default column to Hidden Column
				Move Date Modified from Default column to Hidden Column
				Move Activity Type  from Hidden column to Default column
				Move Activity Date/Time from Hidden column to Default column
			Edit the layouts
				Listview
					Move all existing fields out of Default column to Hidden column
					Move from the Hidden column to the Default Column: Users, Created By, Activty Type and Activity Date/Time
					Save
			Save and Deploy the package
				(Result is a module/bean called rcual_useractivity)
		Repair 
			Quick Repair and Rebuild
  1. Relate the new module to the Users’ module
    Use Studio Tools to add a relationship between Users and User Activity
    Use studio; do NOT do this from within Module Builder since you want the Primary module to be users
    The primary module is the one which has the subpanel showing the relationships
			SuiteCRM->Admin->Developer Tools->Studio->Users->Relationships->Add Relationship
				Type: one to Many
				Related Module: User Activity
				(result is link called users_rcual_useractivity_1  in bean rcual_useractivity)
				(link name can be found in cache/modules/Users/Uservardefs.php)
				Save and Deploy

Now go back in Studio to add the user to the listview of the new module rcual_useractivity

		SuiteCRM->Admin->Developer Tools->Studio->rcual_useractivity->Layouts->listview
			Drag Users from Hidden to Default
			Save	
Repair and Rebuild the installation
		Rebuild Relationships
		Quick Repair and Rebuild

Make the views read-only (ie prevent any user Create,Update or Delete operations) to the useractivty data
Remove the action buttons from the useractivity subpanel in Users so the display is read-only
Edit custom/Extensions/modules/Users/Ext/Layoutdefs/users_rcual_useractivity_1_Users.php

		Comment out the array which defines the action buttons in the useractivity subpanel
		so it now looks like (note the /* and */ markers commenting out the whole array definition section)
			<?php
			 // created: 2019-05-13 16:28:41
			$layout_defs["Users"]["subpanel_setup"]['users_rcual_useractivity_1'] = array (
			  'order' => 100,
			  'module' => 'rcual_useractivity',
			  'subpanel_name' => 'default',
			  'sort_order' => 'asc',
			  'sort_by' => 'id',
			  'title_key' => 'LBL_USERS_RCUAL_USERACTIVITY_1_FROM_RCUAL_USERACTIVITY_TITLE',
			  'get_subpanel_data' => 'users_rcual_useractivity_1',
			  'top_buttons' => 
			  array (
				/*
				0 => 
				array (
				  'widget_class' => 'SubPanelTopButtonQuickCreate',
				),
				1 => 
				array (
				  'widget_class' => 'SubPanelTopSelectButton',
				  'mode' => 'MultiSelect',
				),
				 */
			  ),
			);

Remove the Menu item for Creating useractivity from the useractivity module so it is read-only

		Edit modules/rcual-useractivity/Menu.php 
		Note that this is NOT upgrade safe, but it is a custom module so SuiteCRM upgrades will not touch this module
		Comment out the if section that presents the Edit menu item
		so it now looks like (note the /* and */ markers commenting out the whole if section for the edit menu item)
			if (!defined('sugarEntry') || !sugarEntry) {
				die('Not A Valid Entry Point');
			}

			global $mod_strings, $app_strings, $sugar_config;
			/*
			if(ACLController::checkAccess('rcual_useractivity', 'edit', true)){
				$module_menu[]=array('index.php?module=rcual_useractivity&action=EditView&return_module=rcual_useractivity&return_action=DetailView', $mod_strings['LNK_NEW_RECORD'], 'Add', 'rcual_useractivity');
			}
			*/
			if(ACLController::checkAccess('rcual_useractivity', 'list', true)){
				$module_menu[]=array('index.php?module=rcual_useractivity&action=index&return_module=rcual_useractivity&return_action=DetailView', $mod_strings['LNK_LIST'],'View', 'rcual_useractivity');
			}

Note that the above code hides the navigation option but a user could still directly access the editview by entering
https://{server_address}/index.php?module=rcual_useractivity&action=editview
so to prevent that (again, not upgrade-safe, but see above for why this is not a problem)

			Create a new file at modules/rcual-useractivity/views/view.edit.php  (you will likely need to create the views directory)
				<?php
				require_once('include/MVC/View/views/view.edit.php');
				class rcual_useractivityViewEdit extends SugarView{
					function display(){
						if(!$this->bean->fetched_row){
							sugar_die('Data for this module is created by the system so users have no edit capability'); 
						}
						parent::display();
					}
				}
		Admin->Repair->Repair and Rebuild
  1. Create the script that auto-populates the login/logout data based on user actions
		Create a new file at custom/modules/Users/record_user_login.php
			<?php

			if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

			class record_user_login_class
			{
					function record_user_login_method($bean, $event, $arguments)
					{
						global $timedate;
						
						// Create the entries for the historical list of logic/logout times kept in the User Activity module
						$activityBean = BeanFactory::newBean('rcual_useractivity');
						$activityBean->useractivitytype = "login";
						$activityBean->useractivitytime = $timedate->nowDb();
						$activityBean->save();
						// Load the relationship between the Contacts and the Notes module
						$bean->load_relationship('users_rcual_useractivity_1');
						// Relate the new entry to the user
						$bean->users_rcual_useractivity_1->add($activityBean);
					}
			}
			?>
		Create a new file at custom/modules/Users/record_user_logout.php
			<?php

			if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

			class record_user_logout_class
			{
					function record_user_logout_method($bean, $event, $arguments)
					{
						global $timedate;

						// Create the entries for the historical list of logic/logout times kept in the User Activity module
						$activityBean = BeanFactory::newBean('rcual_useractivity');
						$activityBean->useractivitytype = "logout";
						$activityBean->useractivitytime = $timedate->nowDb();
						$activityBean->save();
						// Load the relationship between the Contacts and the Notes module
						$bean->load_relationship('users_rcual_useractivity_1');
						// Relate the new entry to the user
						$bean->users_rcual_useractivity_1->add($activityBean);
					}
			}
			?>
		Edit the existing file at custom/modules/Users/logic_hooks.php
			Change 
				<?php
				// Do not store anything in this file that is not part of the array or the hook version.  This file will
				// be automatically rebuilt in the future.
				 $hook_version = 1;
				$hook_array = Array();
				// position, file, function
				$hook_array['after_login'] = Array();
				$hook_array['after_login'][] = Array(1, 'SugarFeed old feed entry remover', 'modules/SugarFeed/SugarFeedFlush.php','SugarFeedFlush', 'flushStaleEntries');
				?>
			to  (ie add the last 2 lines)
				<?php
				// Do not store anything in this file that is not part of the array or the hook version.  This file will
				// be automatically rebuilt in the future.
				 $hook_version = 1;
				$hook_array = Array();
				// position, file, function
				$hook_array['after_login'] = Array();
				$hook_array['after_login'][] = Array(1, 'SugarFeed old feed entry remover', 'modules/SugarFeed/SugarFeedFlush.php','SugarFeedFlush', 'flushStaleEntries');
				$hook_array['after_login'][] = Array(98, 'Record last user login date and time', 'custom/modules/Users/record_user_login.php','record_user_login_class', 'record_user_login_method');
				$hook_array['before_logout'][] = Array(99, 'Record last user logout date and time', 'custom/modules/Users/record_user_logout.php','record_user_logout_class', 'record_user_logout_method');
				?>

Do a final clean-up

		Repair and Rebuild
		Using the SuiteCRM web interface
			SuiteCRM -> Admin -> System (section) -> Repair -> Quick Repair and Rebuild
			(when the screen refreshes, scroll to the bottom in case it requires a manual sync of vardefs and database)
		Using the CLI (maybe not necessary every time, but it never hurts to ensure correct permissions)
			cd {root directory of SuiteCRM installation}
			chown -R www-data:www-data .
			chmod -R 755 .
			chmod -R 775 cache custom modules themes data upload
			chmod 775 config_override.php 2>/dev/null

That’s it!

If you change any names of modules or fields, you’ll have to change the instructions to match

Anyone who sees any issues cause by this or has any suggestion for improvement, please feel free to add your comments/feedback.

2 Likes

Looks good. With this module+hook+listview approach, this is basically it.

I once thought of achieving the same goal using the “tracker” table, which already has a nice class to drive it, and would be a natural place to store these login-logout actions. But I never really investigated it. In that case, it would probably be better to make the changes in core SuiteCRM, not as a customization. I think this feature is sufficiently interesting for a wide audience, so that it could go into the product.

Thanks for your complete tutorial, we need more of those!

Thank you very much for the post
Just to let you know I have just set this up and it works perfectly.
Many Thanks

The above code works just fine as is, but I recently had a situation with only 6 users on a new system where, for training purposes, I wanted to be informed when users were actually using the system, without having to go in and constantly check the Login/Logout module.

So I edited the “record_user_login_method” function in the record_user_login.php file, and added code that also emailed me (in addition to creating the log) every time someone logged into the system

The updated record_user_login.php file now has
(Edit email@example.com, text descriptor of email and name_of_system to suit your situation)

<?php
if (!defined('sugarEntry') || !sugarEntry) {
    die('Not A Valid Entry Point');
}
class record_user_login {
    function record_user_login_method($bean, $event, $arguments) {
        global $timedate;
        // Create the entries for the historical list
        // of login/logout times kept in the User Activity module
        $activityBean = BeanFactory::newBean('AYU_useractivity');
        $activityBean->useractivitytype_c = "login";
        $now_time = $timedate->nowDb();
        $activityBean->useractivitytime_c = $now_time;
        $activityBean->save();
        // Load the relationship between the Users and AYU_useractivity module
        $bean->load_relationship('users_ayu_useractivity_1');
        // Relate the new entry to the user
        $bean->users_ayu_useractivity_1->add($activityBean);

        // Optionally, email the system administrator (as a security check)
        // each time a user logs in ... including the administrator
        // Fetch the current user data
        $send_login_email = true;
        if ( $send_login_email ) {
            global $current_user;
            $login_user_user_name = $current_user->user_name;
            $login_user_full_name = $current_user->full_name;
            $login_user_email = $current_user->email1;
            // Get the sugar email engine
            require_once("modules/Emails/Email.php");
            $email = new SugarPHPMailer();
            // Fetch the system email settings so can use system email setup
            // This avoids entering user/password information in the script
            $email_parameters = new Email();
            $system_email = $email_parameters->getSystemDefaultEmail();
            $email->From = $system_email['email'];
            $email->FromName = $system_email['name'];
            // Prepare clean setup
            $email->ClearAllRecipients();
            $email->ClearReplyTos();
            // Establish the MailTo email parameters
            $email->AddAddress('email@example.com', 'text descriptor of email' );
            //Create a Subject line
            $email_subject_line = "Login to name_of_system by $login_user_user_name recorded at $now_time by $login_user_user_name";
            $email->Subject = from_html($email_subject_line);
            // Create the body contents
            $email_body_contents = " User Name: $login_user_user_name \n Real Name: $login_user_full_name \n Email Address: $login_user_email ";
            $email->Body = from_html($email_body_contents);
            $email->IsHTML(false);
            // Initialize the email sending system
            $email->prepForOutbound();
            $email->setMailerForSystem();
            // Send the email and perform an error check on the result
            if(!$email->Send()){
                $GLOBALS['log']->error("Could not send notification email: ". $email->ErrorInfo);
            } else {
                $GLOBALS['log']->info("Login notification email sent");
            }
        }
    }
}

Please someone send code for Users logic hook which will write login, logout to the CRM log file. Also, steps if possible.