File: /home/xedaptot/ai.naniguide.com/app/Model/User.php
<?php
/**
* User class.
*
* Model class for user
*
* LICENSE: This product includes software developed at
* the Acelle Co., Ltd. (http://acellemail.com/).
*
* @category MVC Model
*
* @author N. Pham <[email protected]>
* @author L. Pham <[email protected]>
* @copyright Acelle Co., Ltd
* @license Acelle Co., Ltd
*
* @version 1.0
*
* @link http://acellemail.com
*/
namespace App\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Notifications\ResetPassword;
use App\Model\Setting;
use Illuminate\Http\UploadedFile;
use App;
use AppUrl;
use App\Library\Tool;
use App\Library\Traits\HasUid;
use App\Library\Facades\Hook;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use App\Library\Traits\HasSettingsField;
use League\Pipeline\PipelineBuilder;
use App\Library\GeneralTagResolver;
use App\Library\UserActivationTagResolver;
use App\Library\HtmlHandler\GenerateSpintax;
use App\Library\HtmlHandler\DecodeHtmlSpecialChars;
use App\Library\HtmlHandler\ReplaceBareLineFeed;
use App\Library\HtmlHandler\TransformTag;
use App\Library\HtmlHandler\TransformUrl;
use App\Library\HtmlHandler\MakeInlineCss;
class User extends Authenticatable
{
use \Illuminate\Database\Eloquent\Factories\HasFactory;
use Notifiable;
use HasSettingsField;
use HasUid;
protected static function newFactory()
{
return \Database\Factories\UserFactory::new();
}
/*******
* Important: Storage location: User vs Customer
* One customer (account) may have one or more users
*
* User has its own location for storing:
* + Lock files
* + Assets (which is used by File Manager)
*
* Customer has a common place to store account's assets (available to all users of the same account)
* + Templates
* + Attachments (account-wide, not individual user)
*
*/
public const BASE_DIR = 'app/users';
public const PROFILE_IMAGE_PATH = 'home/avatar';
public const STATUS_ACTIVE = 'active';
public const STATUS_INACTIVE = 'inactive';
// Role
public const ROLE_FULL_ACCESS = 'full-access';
public const ROLE_READ_ONLY = 'read-only';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'email', 'first_name', 'last_name', 'phone'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* Associations.
*
* @var object | collect
*/
/**
* @return BelongsTo<Customer, $this>
*/
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
/**
* @return HasOne<Admin, $this>
*/
public function admin(): HasOne
{
return $this->hasOne(Admin::class);
}
/**
* @return HasOne<BrandVoice, $this>
*/
public function brandVoice(): HasOne
{
return $this->hasOne(BrandVoice::class);
}
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, 'user_role');
}
public function getRole()
{
if ($this->roles()->count() > 1) {
throw new \Exception('User can not have more than one role!');
}
return $this->roles()->first();
}
/**
* Get authenticate from file.
*
* @return string
*/
public static function getAuthenticateFromFile()
{
return ['email' => '', 'password' => ''];
}
/**
* Send regitration activation email.
*
* @return string
*/
public function sendActivationMail($name = null, $planUid = null)
{
$systemTemplate = \App\Model\SystemTemplate::findByAliasOrFail('registration_confirmation_email');
$token = $this->getToken();
$pipeline = new PipelineBuilder();
$pipeline->add(new TransformTag(
new GeneralTagResolver(),
new UserActivationTagResolver($this, $token, $name, $planUid),
));
$pipeline->add(new TransformUrl($messageId = null, $domain = null, $trackLink = false));
$pipeline->add(new DecodeHtmlSpecialChars());
$pipeline->add(new GenerateSpintax());
$pipeline->add(new ReplaceBareLineFeed());
$pipeline->add(new MakeInlineCss($systemTemplate->template->findCssFiles()));
$content = $pipeline->build()->process($systemTemplate->template->content);
$name = is_null($name) ? trans('messages.to_email_name') : $name;
$email = new \Symfony\Component\Mime\Email();
$email->subject(trans('messages.registration_confirmation_email'));
$email->to(new \Symfony\Component\Mime\Address($this->email, $name));
$email->html($content);
$mailer = App::make('xmailer');
$mailer->sendWithDefaultFromAddress($email);
}
/**
* The rules for validation.
*
* @var array
*/
public function rules()
{
$rules = array(
'email' => 'required|email|unique:users,email,'.$this->id.',id',
'first_name' => 'required',
'last_name' => 'required',
'timezone' => 'required',
'language_id' => 'required',
'image' => 'nullable|image',
'phone' => [(Setting::get('user.require_mobile_phone') == 'yes' ? 'required' : 'nullable'), 'regex:/^[\+0-9]{10,15}$/'],
);
if (isset($this->id)) {
$rules['password'] = 'nullable|confirmed|min:5|max:255';
} else {
$rules['password'] = 'required|confirmed|min:5|max:255';
}
return $rules;
}
/**
* The rules for validation.
*
* @var array
*/
public function registerRules()
{
$rules = array(
'email' => 'required|email|unique:users,email,'.$this->id.',id',
'first_name' => 'required',
'last_name' => 'required',
'timezone' => 'required',
'language_id' => 'required',
'phone' => [(Setting::get('user.require_mobile_phone') == 'yes' ? 'required' : 'nullable'), 'regex:/^[\+0-9]{10,15}$/'],
);
if (isset($this->id)) {
$rules['password'] = 'min:5|max:255';
} else {
$rules['password'] = 'required|max:255';
}
return $rules;
}
/**
* The rules for validation via api.
*
* @var array
*/
public function apiRules()
{
return array(
'email' => 'required|email|unique:users,email,'.$this->id.',id',
'first_name' => 'required',
'last_name' => 'required',
'timezone' => 'required',
'language_id' => 'required',
'password' => 'required|min:5',
'phone' => [(Setting::get('user.require_mobile_phone') == 'yes' ? 'required' : 'nullable'), 'regex:/^[\+0-9]{10,15}$/'],
);
}
/**
* The rules for validation via api.
*
* @var array
*/
public function apiUpdateRules($request)
{
$arr = [];
if (isset($request->email)) {
$arr['email'] = 'required|email|unique:users,email,'.$this->id.',id';
}
if (isset($request->first_name)) {
$arr['first_name'] = 'required';
}
if (isset($request->last_name)) {
$arr['last_name'] = 'required';
}
if (isset($request->timezone)) {
$arr['timezone'] = 'required';
}
if (isset($request->language_id)) {
$arr['language_id'] = 'required';
}
if (isset($request->password)) {
$arr['password'] = 'min:5|max:255';
}
return $arr;
}
/**
* User activation.
*
* @return string
*/
public function userActivation()
{
return $this->hasOne('App\Model\userActivation');
}
/**
* Create activation token for user.
*
* @return string
*/
public function getToken()
{
$token = \App\Model\UserActivation::getToken();
$userActivation = $this->userActivation;
if (!$userActivation) {
$userActivation = new \App\Model\UserActivation();
$userActivation->user_id = $this->id;
}
$userActivation->token = $token;
$userActivation->save();
return $token;
}
/**
* Set user is activated.
*
* @return bool
*/
public function setActivated()
{
$this->activated = true;
$this->save();
}
/**
* Bootstrap any application services.
*/
public static function boot()
{
parent::boot();
// Create uid when creating list.
static::creating(function ($item) {
// Create new uid
$uid = uniqid();
$item->uid = $uid;
// Add api token
$item->api_token = str_random(60);
});
}
/**
* Check if user has admin account.
*/
public function isAdmin()
{
return !is_null($this->admin);
}
/**
* Send the password reset notification.
*
* @param string $token
*/
public function sendPasswordResetNotification($token)
{
// $this->notify(new ResetPassword($token, url('password/reset', $token)));
$resetPasswordUrl = url('password/reset', $token);
$htmlContent = '<p>Please click the link below to reset your password:<br><a href="'.$resetPasswordUrl.'">'.$resetPasswordUrl.'</a>';
$email = new \Symfony\Component\Mime\Email();
$email->subject('Password Reset');
$email->to($this->email);
$email->html($htmlContent);
$mailer = App::make('xmailer');
$mailer->sendWithDefaultFromAddress($email);
}
public function getLockPath($path)
{
$base = $this->getBasePath('locks');
if (!\Illuminate\Support\Facades\File::exists($base)) {
\Illuminate\Support\Facades\File::makeDirectory($base, 0777, true, true);
}
return join_paths($base, $path);
}
public function getBasePath($path = null)
{
$base = storage_path(join_paths(self::BASE_DIR, $this->uid));
if (!\Illuminate\Support\Facades\File::exists($base)) {
\Illuminate\Support\Facades\File::makeDirectory($base, 0777, true, true);
}
return join_paths($base, $path);
}
/**
* Generate one time token.
*/
public function generateOneTimeToken()
{
$this->one_time_api_token = generateRandomString(32);
$this->save();
}
/**
* Clear one time token.
*/
public function clearOneTimeToken()
{
$this->one_time_api_token = null;
$this->save();
}
public function getProfileImageUrl()
{
$path = $this->getProfileImagePath();
if (file_exists($path)) {
return route('user.avatar', ['uid' => $this->uid]) . '?v=' . md5_file($path);
}
return AppUrl::asset('images/user-placeholder.svg');
}
public function getProfileImagePath()
{
return $this->getBasePath(self::PROFILE_IMAGE_PATH);
}
/**
* Upload and resize avatar.
*
* @var void
*/
public function uploadProfileImage(UploadedFile $file)
{
// Full path: /storage/app/users/000000/home/avatar
$path = $this->getProfileImagePath();
// File name: avatar
$filename = basename($path);
// The base dir: /storage/app/users/000000/home/
$dirname = dirname($path);
// save to server at /storage/app/users/000000/home/avatar
$file->move($dirname, $filename);
// create thumbnails and replace the original image with the the small-sized thumbnail
$img = \Image::make($path);
$img->fit(120, 120)->save($path);
return $path;
}
public function removeProfileImage()
{
$path = $this->getProfileImagePath();
if (file_exists($path)) {
\Illuminate\Support\Facades\File::delete($path);
}
}
public function deleteAndCleanup()
{
// User's storage location
// For example: storage/app/users/000000/
$path = $this->getBasePath();
if (file_exists($path)) {
Tool::xdelete($path);
}
$this->delete();
}
public function generateOneClickLoginUrl()
{
return action('AppController@autoLogin', [
'api_token' => $this->api_token,
]);
}
public static function scopeSearch($query, $keyword)
{
$query = $query->select('users.*');
// Keyword
if (!empty(trim($keyword))) {
foreach (explode(' ', trim($keyword)) as $keyword) {
$query = $query->where(function ($q) use ($keyword) {
$q->orwhere('users.first_name', 'like', '%'.$keyword.'%')
->orWhere('users.last_name', 'like', '%'.$keyword.'%')
->orWhere('users.email', 'like', '%'.$keyword.'%');
});
}
}
}
public function displayName($lastNameFirst)
{
if ($lastNameFirst) {
return htmlspecialchars(trim($this->last_name.' '.$this->first_name));
} else {
return htmlspecialchars(trim($this->first_name.' '.$this->last_name));
}
}
public function isActivated()
{
return $this->activated;
}
public static function newDefault()
{
$user = new self();
$user->activated = true;
$user->status = self::STATUS_ACTIVE;
return $user;
}
public function fillAttributes($params)
{
$this->fill($params);
}
public function enable()
{
$this->activated = 1;
$this->save();
}
public function disable()
{
$this->activated = 0;
$this->save();
}
// @todo remove this later
public function isReadOnly()
{
return false;
}
public function setRole($role)
{
// for now, one user only has one role
// the same role, do nothing
if ($this->getRole() && $this->getRole()->id == $role->id) {
return;
}
// remove current role if it exists
if ($this->getRole()) {
$this->roles()->detach($this->getRole());
}
// set new role
$this->roles()->save($role);
}
public function hasPermission($permission)
{
// default is access all if has not role
if (!$this->getRole()) {
return false;
}
return $this->getRole()->hasPermission($permission);
}
public function getOnscreenIntros()
{
if (!$this->onscreen_intros) {
return [];
}
return json_decode($this->onscreen_intros, true);
}
public function getOnscreenIntro($screenName)
{
$screenIntros = $this->getOnscreenIntros();
if (!isset($screenIntros[$screenName])) {
return null;
}
return $screenIntros[$screenName];
}
public function setOnscreenIntro($screenName, $data)
{
$screenIntros = $this->getOnscreenIntros();
$screenIntros[$screenName] = $data;
$this->onscreen_intros = json_encode($screenIntros);
$this->save();
}
public function isOnscreenIntroShowed($screenName)
{
$screenIntro = $this->getOnscreenIntro($screenName);
if (!$screenIntro) {
return false;
}
return $screenIntro['showed'];
}
public function setOnscreenIntroShowed($screenName)
{
$screenIntro = $this->getOnscreenIntro($screenName);
if (!$screenIntro) {
$screenIntro = [
'showed' => true,
];
} else {
$screenIntro['showed'] = true;
}
$this->setOnscreenIntro($screenName, $screenIntro);
}
public function generateEmailVerifyCode()
{
$numbers = str_shuffle('0123456789');
$randomString = substr($numbers, 0, 6);
$this->two_factor_verify_code = $randomString;
$this->save();
return $this->two_factor_verify_code;
}
public function getLastSendUntilNowInSeconds()
{
return $this->two_factor_last_send ? \Carbon\Carbon::now()->diffInSeconds($this->two_factor_last_send, $abs = true) : 600;
}
public function getRemainVerifyCountdown()
{
return (60 > $this->getLastSendUntilNowInSeconds()) ? (60 - $this->getLastSendUntilNowInSeconds()) : 0;
}
public function sendVerifyCodeEMail()
{
// resend in 60 seconds
if ($this->getLastSendUntilNowInSeconds() < 60) {
throw new \Exception('Last message sent less than 60 seconds ago. Cannot send messages too quickly.');
}
// code
$code = $this->generateEmailVerifyCode();
$email = new \Symfony\Component\Mime\Email();
$email->subject(trans('messages.2fa.verification_code'));
$email->to($this->email);
$email->html(view('auth.2fa.email.content', [
'code' => $code,
])->render());
$mailer = App::make('xmailer');
$mailer->sendWithDefaultFromAddress($email);
// \Illuminate\Support\Facades\Mail::to($this)->send(new \App\Mail\VerifyEmailSendCode($code));
//
$this->two_factor_last_send = \Carbon\Carbon::now()->timezone('UTC');
$this->save();
}
public function verifyCode($inputCode)
{
$sucess = $this->two_factor_verify_code == trim($inputCode);
if ($sucess) {
// session update
request()->session()->put('two_factor_authenticated', true);
}
return $sucess;
}
public function is2FAAuthenticated()
{
return request()->session()->has('two_factor_authenticated');
}
public function set2FAAuthenticated()
{
// session update
request()->session()->put('two_factor_authenticated', true);
}
public function getInlineGAImage()
{
return \Google2FA::getQRCodeInline(
\App\Model\Setting::get('site_name'),
$this->email,
$this->google_2fa_secret_key,
);
}
public function is2FAEnabled()
{
return $this->enable_2fa && ($this->is2FAEmailEnabled() || $this->is2FAGoogleAuthentictorEnabled());
}
public function is2FAEmailEnabled()
{
return $this->enable_2fa_email;
}
public function is2FAGoogleAuthentictorEnabled()
{
return $this->enable_2fa_google_authenticator && $this->google_2fa_secret_key;
}
public function saveUser(
$email,
$password,
$passwordConfirmation,
$first_name,
$last_name,
$image,
$role_uid,
$phone = null
) {
$rules = [
'email' => 'required|email|unique:users,email,'.$this->id.',id',
'first_name' => 'required',
'last_name' => 'required',
'image' => 'nullable|image',
'role_uid' => 'required',
'phone' => [(Setting::get('user.require_mobile_phone') == 'yes' ? 'required' : 'nullable'), 'regex:/^[\+0-9]{10,15}$/'],
];
if (isset($this->id)) {
$rules['password'] = 'nullable|confirmed|min:5|max:255';
} else {
$rules['password'] = 'required|confirmed|min:5|max:255';
}
// fill
$this->email = $email;
if ($password) {
$this->password = $password;
}
$this->first_name = $first_name;
$this->last_name = $last_name;
$this->phone = $phone;
$validator = \Validator::make([
'email' => $email,
'password' => $password,
'password_confirmation' => $passwordConfirmation,
'first_name' => $first_name,
'last_name' => $last_name,
'image' => $image,
'role_uid' => $role_uid,
'phone' => $phone,
], $rules);
// validate
if ($validator->fails()) {
return [false, $validator->errors()];
}
// find role
$role = Role::findByUid($role_uid);
// Update password
if (!empty($password)) {
$this->password = bcrypt($password);
}
// is_new check
$isNew = !$this->id;
// save
$this->save();
// Hook
if ($isNew) {
Hook::fire('user_added', [$this]);
}
// Upload and save image
if ($image) {
if ($image->isValid()) {
// Remove old images
$this->uploadProfileImage($image);
}
}
// role is required
$this->setRole($role);
return [true, $validator->errors()];
}
}