PHPackages                             remp/crm-users-module - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. remp/crm-users-module

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

remp/crm-users-module
=====================

CRM Users Module

4.4.0(5mo ago)152.9k↓14.3%11[1 issues](https://github.com/remp2020/crm-users-module/issues)[1 PRs](https://github.com/remp2020/crm-users-module/pulls)1MITPHPPHP ^8.1CI failing

Since Apr 30Pushed 5mo ago7 watchersCompare

[ Source](https://github.com/remp2020/crm-users-module)[ Packagist](https://packagist.org/packages/remp/crm-users-module)[ Docs](https://remp2030.com)[ RSS](/packages/remp-crm-users-module/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (3)Versions (85)Used By (1)

CRM Users Module
================

[](#crm-users-module)

[![Translation status @ Weblate](https://camo.githubusercontent.com/2b9e151b997501fde4b5ad77670eb818b6319dba94f71356dab6c4daf8e213c2/68747470733a2f2f686f737465642e7765626c6174652e6f72672f776964676574732f72656d702d63726d2f2d2f75736572732d6d6f64756c652f7376672d62616467652e737667)](https://hosted.weblate.org/projects/remp-crm/users-module/)

This documentation describes API handlers and CLI commands provided by this module for others to use. It expects your application is based on the [CRM skeleton](https://github.com/remp2020/crm-skeleton)provided by us.

Installing module
-----------------

[](#installing-module)

We recommend using Composer for installation and update management.

```
composer require remp/crm-users-module
```

Enabling module
---------------

[](#enabling-module)

Add installed extension to your `app/config/config.neon` file.

```
extensions:
	users: Crm\UsersModule\DI\UsersModuleExtension
```

Generate ACL
------------

[](#generate-acl)

User access resources are used to control access rights of admin groups in CRM admin. These resources are generated from methods `render*`, `action*`, `handle*` of all presenters extending `Crm\AdminModule\Presenters\AdminPresenter`.

To generate run command:

```
php bin/command.php user:generate_access
```

This command should be run everytime new version is released to validate &amp; generate new resources.

All new resources are automatically assigned to `superadmin` admin group - check seeder `Crm\UsersModule\Seeders\UsersSeeder`.

Other admin groups are not affected. New resources have to be assigned either manually via admin interface () or seeded within your module.

### ACL - `admin-access-level`

[](#acl---admin-access-level)

*These annotations are optional and do not affect resolving access rights to resource.*

To ease assigning of access rights to admin groups (), level of access resource can be specified as method annotation `admin-access-level`. CRM uses now two levels `read` and `write`. Level `write` indicates that this method can be used to create, update or remove entity. Rest of resources has level `read`.

Example:

```
use Crm\AdminModule\Presenters\AdminPresenter;

class ExampleAdminPresenter extends AdminPresenter {

    /**
     * @admin-access-level read
     */
    public function renderDefault()
    {
    }

    /**
     * @admin-access-level write
     */
    public function renderEdit()
    {
    }
}
```

If annotation `admin-access-level` is missing, no level is displayed on page where resources/right are assigned to admin groups ().

### Cleanup

[](#cleanup)

Command has also cleanup option:

```
php bin/command.php user:generate_access --cleanup
```

After resources are generated, it will compare current ACL resources and actions with previous state. Orphaned ACL resources will be removed from database. Useful when module is uninstalled or admin presenter / action is removed.

> **WARNING**: This option doesn't fix ACL after resource is moved. That should be handled by module when resource is moved / renamed (eg. by migration).

Single sign-on
--------------

[](#single-sign-on)

### Google Sign-In

[](#google-sign-in)

Users module supports Google Sign-In authentication using the Authorization code flow and ID token.

#### Configuration

[](#configuration)

Any application that uses Google Sign-In must have authorization credentials that identify the application to Google's OAuth 2.0 server. To set up credentials, please go to Google [Credentials page](https://console.developers.google.com/apis/credentials).

After acquiring credentials, put them to `neon` configuration file using the following format:

```
users:
	sso:
	    google:
	        client_id: CLIENT_ID
	        client_secret: CLIENT_SECRET
```

Last step is to **enable** Google Sign-In in CRM settings in `/admin/config-admin/` Authentication section.

#### ID Token

[](#id-token)

ID Token is a Google-signed JWT token holding user information (see [the documentation](https://developers.google.com/identity/sign-in/web/backend-auth)). This module provides an [API endpoint](#post-apiv1usersgoogle-token-sign-in) to validate the token and match it to an existing user (or create a new one) using user's email address.

#### Authorization code flow

[](#authorization-code-flow)

Standard OAuth2 Authorization code flow is initiated when user is redirected to `http://crm.press/users/google/sign` URL.

An optional parameter is `url`, which is a URL to redirect to after the successful login. `url` is validated against current CRM domain - `url` has to share at least the second level domain, e.g. if your CRM is available at `crm.yoursystem.com`, any domain passing `*.yoursystem.com` will be considered as a valid redirect URI.

#### Example

[](#example)

HTML button to initiate Google Sign-In:

```
Google Sign-In
```

### Apple Sign-In

[](#apple-sign-in)

Users module supports Apple Sign-In authentication using the Authorization code flow and ID token.

#### Configuration

[](#configuration-1)

Any application that uses Apple Sign-In must have authorization credentials that identify the application to Apple. For more information, please go to Apple [Get started page](https://developer.apple.com/sign-in-with-apple/get-started/).

After acquiring credentials, put them to `neon` configuration file using the following format:

```
users:
	sso:
	    apple:
	        client_id: CLIENT_ID # default client ID used for signing in on the web
	        trusted_client_ids: [CLIENT_ID] # other trusted client IDs using the Apple ID to sign in (e.g. mobile apps)
```

Last step is to **enable** Apple Sign-In in CRM settings in `/admin/config-admin/` Authentication section.

#### ID Token

[](#id-token-1)

ID Token is a Apple-signed JWT token holding user information. This module provides an [API endpoint](#post-apiv1usersapple-token-sign-in) to validate the token and match it to an existing user (or create a new one) using user's email address.

#### Authorization code flow

[](#authorization-code-flow-1)

Standard OAuth2 Authorization code flow is initiated when user is redirected to `http://crm.press/users/apple/sign` URL.

An optional parameter is `url`, which is a URL to redirect to after the successful login. `url` is validated against current CRM domain - `url` has to share at least the second level domain, e.g. if your CRM is available at `crm.yoursystem.com`, any domain passing `*.yoursystem.com` will be considered as a valid redirect URI.

#### Example

[](#example-1)

HTML button to initiate Apple Sign-In:

```
Apple Sign-In
```

### Allow domains in url redirect

[](#allow-domains-in-url-redirect)

To enable more domains in url redirect, please add the following configuration to your configuration `neon` file:

```
redirectValidator:
	setup:
		- addAllowedDomains('another.domain.com', 'some.other.domain.net')
		- addAllowedDomains('domain.example.com')
```

Note: `redirectValidator` is named service `Crm\ApplicationModule\Router\RedirectValidator` registered by `ApplicationModule`.

Secured admin login
-------------------

[](#secured-admin-login)

### Required Google Sign-In

[](#required-google-sign-in)

To enhance security, one may require all users with admin role to log-in using Google Sign-In, if they want to access admin interface. User verification security then relies on Google security mechanisms to identify potential abuse.

To turn on this option (called "**Secured login**"):

- [Register](https://github.com/remp2020/crm-skeleton#registereventhandlers) following event handler to one of your internal modules so the sign-in process correctly flags the source of sign-in (e.g. Google) secure: ```
    public function registerEventHandlers(\League\Event\Emitter $emitter)
    {
        // ...
        $emitter->addListener(
            \Crm\UsersModule\Events\UserSignInEvent::class,
            $this->getInstance(\Crm\UsersModule\Events\SecureAccessSignInEventHandler::class)
        );
    );
    ```

    - If you want more control or different level of security, create your own `SecureAccessSignInEventHandler` and implement your custom rules.
- Check Authentication section of CRM admin settings and enable *Secured login*.
- After enabling the option, each such user has to be acknowledged by adding `secure_login_allowed` flag to `user_meta` table.

### Two-factor authentication

[](#two-factor-authentication)

Currently, 2FA authentication is not implemented.

Data retention configuration
----------------------------

[](#data-retention-configuration)

You can configure time before which `application:cleanup` deletes old repository data and column which it uses by using (in your project configuration file):

```
autoLoginTokensRepository:
	setup:
		- setRetentionThreshold('now', 'valid_at')
changePasswordsLogsRepository:
	setup:
		- setRetentionThreshold('-12 months')
userActionsLogRepository:
	setup:
		- setRetentionThreshold('-12 months')
```

AccessTokenAuthenticator
------------------------

[](#accesstokenauthenticator)

UsersModule generates an access token for every successful user authentication. This token can be used to authenticate the user in API calls.

You can log the user into the CRM automatically if you have such token thanks to the [`AccessTokenAuthenticator`](src/Authenticator/AccessTokenAuthenticator.php).

### How to use

[](#how-to-use)

CRM checks if there's a cookie called `n_token` and extracts the value from it. If the value is valid access token (it's still present in the `access_tokens` table), and it doesn't belong to admin account, it logs user in automatically without requesting username or password.

This comes handy in case your login process is handled on other domain (e.g. in your CMS via CRM's API) and you want your users to get logged in only once.

Events
------

[](#events)

### NewUserEvent

[](#newuserevent)

`NewUserEvent` is emitted for all types of new users created - both regular (claimed) and unclaimed.

### UserRegisteredEvent

[](#userregisteredevent)

`UserRegisteredEvent` is emitted when a regular user is created through `UserBuilder`. Unclaimed user is not considered to be a regular user.

If you call `UsersRepository::add` directly in your own extension and it's a regular user, make sure you emit `UserRegisteredEvent` at the end of the process.

API documentation
-----------------

[](#api-documentation)

All examples use `http://crm.press` as a base domain. Please change the host to the one you use before executing the examples.

All examples use `XXX` as a default value for authorization token, please replace it with the real tokens:

- *API tokens.* Standard API keys for server-server communication. It identifies the calling application as a whole. They can be generated in CRM Admin (`/api/api-tokens-admin/`) and each API key has to be whitelisted to access specific API endpoints. By default the API key has access to no endpoint.
- *User tokens.* Generated for each user during the login process, token identify single user when communicating between different parts of the system. The token can be read:
    - From `n_token` cookie if the user was logged in via CRM.
    - From the response of [`/api/v1/users/login` endpoint](#post-apiv1userslogin) - you're free to store the response into your own cookie/local storage/session.

API responses can contain following HTTP codes:

ValueDescription200 OKSuccessful response, default value400 Bad RequestInvalid request (missing required parameters)403 ForbiddenThe authorization failed (provided token was not valid)404 Not foundReferenced resource wasn't foundIf possible, the response includes `application/json` encoded payload with message explaining the error further.

---

#### GET `/api/v1/user/info`

[](#get-apiv1userinfo)

API call returns basic user information and meta information, based on provided user token.

##### *Headers:*

[](#headers)

NameValueRequiredDescriptionAuthorizationBearer *String*yesUser token.##### *Example:*

[](#example-2)

```
curl -v –X GET http://crm.press/api/v1/user/info \
-H "Content-Type:application/json" \
-H "Authorization: Bearer XXX"
```

Response:

```
{
    "status": "ok",
    "user": {
        "id": 1,
        "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8",
        "email": "admin@example.com",
        "confirmed_at": "2021-01-01T10:00:00+01:00", // RFC3339 date or NULL; user confirmation date
        "first_name": "Test",
        "last_name": "Admin"
    },
    "user_meta": {
        "newsletter_subscribed": "1"
    }
}
```

This was a response from default `UserAuthenticator`. If your application use some custom implementation of authenticator (e.g. `FooAuthenticator`), the authenticator can add extra parameters to the response:

```
{
    "status": "ok",
    "user": {
        // ...
    },
    "foo": {
        "external_id": "baz",
        "custom_flag": true
    }
}
```

---

#### POST `/api/v1/users/login`

[](#post-apiv1userslogin)

API call verifies provided credentials and returns user token.

##### *Params:*

[](#params)

NameValueRequiredDescriptionEmail*String*yesUser's email address.Password*String*yesUser's plain text password.Source*String*noSource of the login indicating which implementation
of *UserAuthenticator* to verify credentials.
The caller is responsible for providing source of the credentials
so the handler know which implementation can handle the verification.device\_token*String*noToken used for pairing mobile device to users access tokens. Can be generated by [`users/get-device-token`](#post-apiv1usersget-device-token).##### *Example:*

[](#example-3)

```
curl 'http://crm.press/api/v1/users/login/' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=admin%40admin.sk&password=password'
```

Success response:

```
{
    "status": "ok",
    "user": {
        "id": 9,
        "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8",
        "email": "admin@crm.press",
        "confirmed_at": "2021-01-01T10:00:00+01:00", // RFC3339 date or NULL; user confirmation date
        "first_name": "Test",
        "last_name": "Admin",
        "roles": ["redaktor", "superadmin"] // admin roles
    },
    "user_meta": {
        "key": "value" // string
    },
    "access": {
        "token": "762eec3fe9f20d87cf865cb40cf6458b" // user token
    }
}
```

Invalid credentials response:

```
{
    "status": "error",
    "error": "auth_failed",
    "message": "Zadané heslo sa nezhoduje s našimi záznamami. Prihláste sa, prosím, tak, ako na webe Denníka N."
}
```

---

#### POST `/api/v1/users/logout`

[](#post-apiv1userslogout)

API call that logs out the authenticated user. If user is authenticated using device token, all associated access tokens are removed too.

##### *Headers:*

[](#headers-1)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAccess token or device token##### *Example:*

[](#example-4)

```
curl 'http://crm.press/api/v1/users/logout' \
  -H 'Authorization: Bearer 7973a4b16be01e25d9f0759c180911af' \
  -H 'Accept: application/json'
```

Success response:

```
{
    "status": "ok"
}
```

---

#### POST `/api/v1/users/email` (DEPRECATED: Use `/api/v2/users/email` instead.)

[](#post-apiv1usersemail-deprecated-use-apiv2usersemail-instead)

API calls checks whether provided email address is valid and available to use (for possible registration).

Additionally it checks whether the provided password is valid for given email address or not. It doesn't login the user into the system and it also doesn't return any user token, it only verifies the password if it was provided.

##### *Params:*

[](#params-1)

NameValueRequiredDescriptionEmail*String*yesEmail to verifyPassword*String*noPassword to verify##### *Example:*

[](#example-5)

```
curl -v –X GET http://crm.press/api/v1/users/email \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=admin%40admin.sk'
```

Response when email is already taken:

```
{
    "email": "admin@crm.press", // String; requested email
    "status": "taken", // String; allowed values ["available", "taken"]
    "id": 9, // Integer; ID of user if email is taken
    "password": null // Boolean; set only if password was provided in request
}
```

Response when email is available:

```
{
    "email": "admin@admin.cz",
    "status": "available",
    "id": null,
    "password": null
}
```

---

#### POST `/api/v2/users/email`

[](#post-apiv2usersemail)

API calls checks whether provided email address is valid and available to use (for possible registration).

Additionally it checks whether the provided password is valid for given email address or not. It doesn't login the user into the system and it also doesn't return any user token, it only verifies the password if it was provided.

##### *Params:*

[](#params-2)

NameValueRequiredDescriptionEmail*String*yesEmail to verifyPassword*String*noPassword to verify##### *Example:*

[](#example-6)

```
curl -v –X GET http://crm.press/api/v2/users/email \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=admin%40admin.sk'
```

Response when email is already taken:

```
{
    "email": "admin@crm.press", // String; requested email
    "status": "taken", // String; allowed values ["available", "taken"]
    "id": 9, // Integer; ID of user if email is taken
    "password": null // Boolean; set only if password was provided in request
}
```

Response when email is available:

```
{
    "email": "admin@admin.cz",
    "status": "available",
    "id": null,
    "password": null
}
```

---

#### POST `/api/v1/users/email-check`

[](#post-apiv1usersemail-check)

API call checks whether provided email address is valid and available to use (for possible registration).

##### *Headers:*

[](#headers-2)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token##### *Params:*

[](#params-3)

NameValueRequiredDescriptionEmail*String*yesEmail to verify##### *Example:*

[](#example-7)

```
curl -v –X GET http://crm.press/api/v1/users/email-check \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=admin%40admin.sk'
```

Response when email is already taken:

```
{
    "email": "admin@admin.sk", // String; requested email
    "id": 9, // Integer; ID of user if email is taken
    "status": "taken", // String; allowed values ["available", "taken"]
}
```

Response when email is available:

```
{
    "email": "admin@admin.cz",
    "status": "available",
}
```

---

#### POST `/api/v1/users/create`

[](#post-apiv1userscreate)

API for registration of user into the system. Password is generated automatically by the system and sent to user by email.

When the user is registered, he/she is automatically logged in and user token is also returned.

##### *Headers:*

[](#headers-3)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token##### *Params:*

[](#params-4)

NameValueRequiredDescriptionemail*String*yes-password*String*no-first\_name*String*no-last\_name*String*no-ext\_id*Integer*noUser identificator from external systemsource*String*noSource of user registration - string label groupping users registered via the same source.referer*String*noReferer URL to indicate where user got registered.note*String*noNote visible only to administratorssend\_email*String*noFlag whether to send welcome email after the registration. If not provided, the system will send an email.disable\_email\_validation*Boolean*noFlag whether to bypass email address validation. If not provided, the system will validate email address.device\_token*String*noToken used for pairing mobile device to users access tokens. Can be generated by [`users/get-device-token`](#post-apiv1usersget-device-token).unclaimed*Boolean*noFlag whether to create unclaimed user. If not provided, standard user will be created.newsletters\_subscribe*Boolean*noFlag whether to subscribe registered user to newsletters.locale*String*noUser locale.##### *Example:*

[](#example-8)

```
curl -v –X GET http://crm.press/api/v1/users/create \
  -H 'Authorization: Bearer XXX' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=user%40user.sk'
```

Success response:

```
{
    "status": "ok",
    "user": {
        "id": 101,
        "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8",
        "email": "user@crm.press",
        "confirmed_at": "2021-01-01T10:00:00+01:00", // RFC3339 date or NULL; user confirmation date
        "first_name": null,
        "last_name": null,
        "roles": [] // admin roles
    },
    "access": {
        "token": "762eec3fe9f20d87cf865cb40cf6458c" // user token
    }
}
```

---

#### POST `/api/v1/users/update`

[](#post-apiv1usersupdate)

API for updating user info.

##### *Headers:*

[](#headers-4)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token##### *Params:*

[](#params-5)

NameValueRequiredDescriptionuser\_id*String*yesID of the user to updateemail*String*no-password*String*no-ext\_id*Integer*noUser identificator from external systemdisable\_email\_validation*Boolean*noFlag whether to bypass email address validation. If not provided, the system will validate email address.locale*String*noUser locale.##### *Example:*

[](#example-9)

```
curl -v –X GET http://crm.press/api/v1/users/update \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'user_id=42&email=user%40user.sk'
```

Success response:

```
{
    "status": "ok",
    "user": {
        "id": 101,
        "email": "user@crm.press",
        "confirmed_at": "2021-01-01T10:00:00+01:00" // RFC3339 date or NULL; user confirmation date
    }
}
```

---

#### GET `/api/v1/users/add-to-group`

[](#get-apiv1usersadd-to-group)

Adds user to the provided group. Group serves for artificial user groupping based on your arbitrary criteria.

You can list your available groups in CRM admin at `/users/groups-admin/`.

##### *Headers:*

[](#headers-5)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-6)

NameValueRequiredDescriptionemail*String*yesEmail to add to user group.group\_id*Integer*yesID of user group to be used.##### *Example:*

[](#example-10)

```
curl -X POST http://crm.press/api/v1/users/add-to-group \
  -H 'Authorization: Bearer XXX' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'email=user%40user.sk&group_id=1'
```

Response:

```
{
    "status": "ok"
}
```

---

#### GET `/api/v1/users/remove-from-group`

[](#get-apiv1usersremove-from-group)

Removes the user from selected group. Group serves for artificial user groupping based on your arbitrary criteria.

You can list your available groups in CRM admin at `/users/groups-admin/`.

##### *Headers:*

[](#headers-6)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-7)

NameValueRequiredDescriptionemail*String*yesEmail to remove from group.group\_id*Integer*yesID of user group to be removed from.##### *Example:*

[](#example-11)

```
curl -X POST http://crm.press/api/v1/users/remove-from-group \
  -H 'Authorization: Bearer XXX' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'email=user%40user.sk&group_id=1'
```

Response:

```
{
    "status": "ok"
}
```

---

#### GET `/api/v1/users/addresses`

[](#get-apiv1usersaddresses)

Lists all user addresses. User is identified by email address.

##### *Headers:*

[](#headers-7)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-8)

NameValueRequiredDescriptionemail*String*yesUser email.type*String*noType of address - types of addresses are managed by modules (e.g. `InvoiceModule` adds support for `invoice` address type.##### *Example:*

[](#example-12)

```
curl -X GET \
  'http://crm.press/api/v1/users/addresses?email=user@crm.press' \
  -H 'Authorization: Bearer XXX' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'email=user%40user.sk'
```

Response:

```
{
    "status": "ok",
    "addresses": [
        {
            "user_id": 10, // Integer; ID of user
            "type": "print", // String; type of address
            "created_at": "2019-03-08T11:37:45+01:00", // RFC3339 date; address creation date
            "email": "user@crm.press", // String; email address of user
            "company_name": "", // String; company name
            "phone_number": "0800123456", // String; phone number (not validated)
            "company_id": "", // String: company ID
            "tax_id": "", // String: company tax ID
            "vat_id": "", // String: company vat ID
            "first_name": "Test", // String: first name of address (can be different from user's first name)
            "last_name": "User", // String; last name of address (can be different from user's first name)
            "street": "10th street", // String: street name
            "number": "368", // String; street number
            "zip": "81105", // String: zip code
            "city": "Bratislava", // String; city
            "country": "Slovensko" // String: user-friendly country name (internally represented by reference)
        }
    ]
}
```

---

#### POST `/api/v1/users/address`

[](#post-apiv1usersaddress)

Creates new address for given user.

##### *Headers:*

[](#headers-8)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-9)

NameValueRequiredDescriptionemail*String*yesUser email.type*String*yesType of address - types of addresses are managed by modules (e.g. `InvoiceModule` adds support for `invoice` address type.first\_name*String*noFirst name.last\_name*String*noLast name.address*String*noStreet name.number*String*noStreet number.zip*String*noZIP code.city*String*noCity.country\_iso*String*noCountry ISO 3166-1 alpha-2 code. Default country is used if not providedcompany\_name*String*noName of the company (if it's corporate address)company\_id*String*noID of company (if it's corporate address)tax\_id*String*noTax ID of company (if it's corporate address)vat\_id*String*noVAT ID of company (if it's corporate address)phone\_number*String*noPhone number.##### *Example:*

[](#example-13)

```
curl -X POST \
  http://crm.press/api/v1/users/address \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'email=user%40user.sk&type=invoice&first_name=AdrName&last_name=AdrLastName&address=11th%20str.&number=112&zip=81105&city=Bratislava'
```

Response:

```
{
    "status": "ok",
    "address": {
        "id": 26929 // Integer; address ID
    }
}
```

---

#### GET `/api/v1/user/addresses`

[](#get-apiv1useraddresses)

Lists all user's own addresses. User is identified from provided user token.

##### *Headers:*

[](#headers-9)

NameValueRequiredDescriptionAuthorizationBearer *String*yesUser token.##### *Params:*

[](#params-10)

NameValueRequiredDescriptiontype*String*noType of address - types of addresses are managed by modules (e.g. `InvoiceModule` adds support for `invoice` address type.##### *Example:*

[](#example-14)

```
curl -X GET \
  'http://crm.press/api/v1/users/addresses?type=print' \
  -H 'Authorization: Bearer XXX'
```

Response:

```
{
    "status": "ok",
    "addresses": { // Object; map of addresses keyed by addressId, value is address represented by single string
        "1235": "John Smith, Václavské náměstí 123, Praha 12345, CZ"
    }

}
```

---

#### POST `/api/v1/users/change-address-request`

[](#post-apiv1userschange-address-request)

Creates new address change request for given type of address and user. Change request might still need to be approved. You should check if the address with given type exists before calling this - if not, create the address first via [`users/address`](#post-apiv1usersaddress) API call.

##### *Headers:*

[](#headers-10)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-11)

NameValueRequiredDescriptionemail*String*yesUser email.type*String*yesType of address - types of addresses are managed by modules (e.g. `InvoiceModule` adds support for `invoice` address type.first\_name*String*noFirst name.company\_name*String*noName of the company (if it's corporate address)last\_name*String*noLast name.street*String*noStreet name.number*String*noStreet number.zip*String*noZIP code.city*String*noCity.country\_iso*String*noCountry ISO 3166-1 alpha-2 code. Default country is used if not providedcountry\_id*String*no**Deprecated** and will be removed. Replaced with `country_iso`.phone\_number*String*noPhone number.company\_id*String*noID of company (if it's corporate address)company\_tax\_id*String*noTax ID of company (if it's corporate address)company\_vat\_id*String*noVAT ID of company (if it's corporate address)##### *Example:*

[](#example-15)

```
curl -X POST \
  http://crm.press/api/v1/users/change-address-request \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'email=user%40user.sk&type=print&first_name=AdrName&last_name=AdrLastName&street=11th%20str.&number=112&zip=81105&city=Bratislava'
```

Response:

```
{
    "status": "ok",
    "address": {
        "id": 26929 // Integer; address ID
    }
}
```

If the address with given type doesn't exist yet, HTTP 400 is returned with following message:

```
{
    "status": "error",
    "message": "Parent address not found"
}
```

---

#### POST `/api/v1/users/list`

[](#post-apiv1userslist)

Lists information of requested users (identified by user IDs). Endpoint requires pagination parameter to be included and paginates the result by 1000 users in one response.

Anonymized users are excluded from list. Deactivated users can be included by using `include_deactivated` flag.

##### *Headers:*

[](#headers-11)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-12)

NameValueRequiredDescriptionuser\_ids*String*yesJSON-encoded array of requested user IDs.page*Integer*yesPage number to retrieve (starting with 1).include\_deactivated*Boolean*noFlag indicating that deactivated users should be listed too. Doesn't affect anonymized users - they are always excluded.with\_uuid*Boolean*noFlag indicating that user UUID should be included in response.##### *Example:*

[](#example-16)

```
curl -X POST \
  http://crm.press/api/v1/users/list \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'user_ids=%5B9%2C10%5D&page=1'
```

Response:

```
{
    "status": "ok",
    "page": 1, // Integer; requested page number
    "totalPages": 1, // Integer; total page count
    "totalCount": 2, // Integer; total record count
    "users": { // Object; map of users keyed by userId with value object containing user data
        "9": {
            "id": 9, // Integer; ID of user
            "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8", // String; UUID of user (only if with_uuid=true)
            "email": "admin@crm.press" // String; email of user
        },
        "10": {
            "id": 10,
            "uuid": "d8f5c2a1-4b3e-4d2c-8e9f-1a2b3c4d5e6f", // String; UUID of user (only if with_uuid=true)
            "email": "user@crm.press"
        }
    }
}
```

---

#### POST `/api/v1/users/confirm`

[](#post-apiv1usersconfirm)

Confirms user based on given email address.

##### *Headers:*

[](#headers-12)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-13)

NameValueRequiredDescriptionemail*String*yesEmail of user to confirm. Unconfirmed users might be deleted in the future or won't get the emails.##### *Example:*

[](#example-17)

```
curl -X POST \
  http://crm.press/api/v1/users/confirm \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d email=admin%40admin.sk
```

Response:

```
{
    "status": "ok"
}
```

If the user with given email doesn't exist , HTTP 404 is returned with following code:

```
{
    "status": "error",
    "code": "user_not_found"
}
```

---

#### POST `/api/v1/user/delete`

[](#post-apiv1userdelete)

Deletes personal data and makes an account anonymous. If the account cannot be deleted, HTTP 403 is returned.

##### *Headers:*

[](#headers-13)

NameValueRequiredDescriptionAuthorizationBearer *String*yesUser token.##### *Example:*

[](#example-18)

```
curl -X POST \
  http://crm.press/api/v1/user/delete \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache'
```

Response:

- `204`: Returned when the user was deleted. No body is returned.
- `403`: Returned when the user cannot be deleted:

    ```
    {
      "status": "error",
      "code": "user_delete_protected",
      "message": "Unable to delete user due to system protection configuration",
      "reason": "Account cannot be deleted automatically. Please contact customer support." // reason why the deletion wasn't executed, can be displayed to the user (it's translated)
    }
    ```

---

#### GET `/api/v1/users/touch`

[](#get-apiv1userstouch)

API call to refresh cached user's data.

##### *Headers:*

[](#headers-14)

NameValueRequiredDescriptionAuthorizationBearer *String*yesUser token.##### *Example:*

[](#example-19)

```
curl -v –X GET http://crm.press/api/v1/users/touch \
-H "Authorization: Bearer XXX"
```

Response:

```
{
    "status": "ok",
    "message": "User touched"
}
```

---

### USER META INFORMATION API

[](#user-meta-information-api)

The concept of meta user's information is to provide the way how to store user related data without need of the database structure changes.

Meta information for user is stored as a pair **key** - **value** and respect two rules:

- One value for one key
- More unique keys for one user

Public property (**is\_public**) defines the availability of meta information for the visual components of CRM administration and the availability for other modules by data providers.

#### POST `/api/v1/user-meta/upsert`

[](#post-apiv1user-metaupsert)

Create or update the meta information for given user.

##### *Headers:*

[](#headers-15)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-14)

NameValueRequiredDescriptionuser\_id*Integer*yesID of user to add/update meta information.key*String*yesMeta information key to set.value*String*yesMeta information value to set.is\_public*Boolean*noMeta information property public to set.##### *Example:*

[](#example-20)

```
curl -X POST \
  http://crm.press/api/v1/user-meta/upsert \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 12345,
    "key": "foo",
    "value": "bar",
    "is_public": false
  }'
```

Response:

```
{
    "key": "foo",
    "value": "bar",
    "is_public": false
}
```

---

#### POST `/api/v1/user-meta/list`

[](#post-apiv1user-metalist)

Return all public meta information of specified user. You can specify meta information by using the meta information key.

##### *Headers:*

[](#headers-16)

NameValueRequiredDescriptionAuthorizationBearer *String*yesUser or API token.##### *Params:*

[](#params-15)

NameValueRequiredDescriptionuser\_id*Integer*noID of user. Used only when API token is provided, otherwise user is determined from User token.key*String*noThe meta information key of user to filter by.##### *Example:*

[](#example-21)

```
curl -X GET \
  http://crm.press/api/v1/user-meta/list?user_id=12345 \
  -H 'Authorization: Bearer XXX' \
  -H 'Accept: application/json'
```

Response:

```
[
    {
        "user_id": 123,
        "key" : "foo",
        "value" : "bar"
    },
    {
        "user_id": 123,
        "key" : "fooz",
        "value" : "1"
    }
]
```

---

#### POST `/api/v1/user-meta/key-users`

[](#post-apiv1user-metakey-users)

Return all users with specified meta information key and value.

##### *Headers:*

[](#headers-17)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-16)

NameValueRequiredDescriptionkey*String*yesThe meta key information of user to filter by.value*String*noThe meta value information of user to filter by.##### *Example:*

[](#example-22)

```
curl -X POST \
  http://crm.press/api/v1/user-meta/key-users \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -d '{
    "key": "foo"
  }'
```

Response:

```
[
    {
        "user_id" : 1,
        "value" : "bar"
    },
    {
        "user_id" : 2,
        "value" : "friend"
    }
]
```

---

#### POST `/api/v1/user-meta/delete`

[](#post-apiv1user-metadelete)

Delete the meta information of user by key. You can delete the meta information of user also only with specific value.

##### *Headers:*

[](#headers-18)

NameValueRequiredDescriptionAuthorizationBearer *String*yesAPI token.##### *Params:*

[](#params-17)

NameValueRequiredDescriptionuser\_id*Integer*yesThe ID of user.key*String*yesThe meta information key to delete by.value*String*noThe meta information value to delete by.##### *Example:*

[](#example-23)

```
curl -X POST \
  http://crm.press/api/v1/user-meta/delete \
  -H 'Authorization: Bearer XXX' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 12345,
    "key": "gdpr"
  }'
```

---

#### POST `/api/v1/users/autologin-token-login`

[](#post-apiv1usersautologin-token-login)

API call verifies provided autologin token and returns user identity and token.

##### *Params:*

[](#params-18)

NameValueRequiredDescriptionautologin\_token*String*yesUser's autologin token.device\_token*String*yesDevice token acquired by `/api/v1/users/get-device-token`.source*String*noSource identifying originating client. If provided, CRM will prefix `source` with `api+` prefix; otherwise CRM will use `api` as a default value for `source`.##### *Example:*

[](#example-24)

```
curl -X POST \
  http://crm.press/api/v1/users/autologin-token-login \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'autologin_token=f8fb5c8d41e454852c0049cfe1031ac1&source=ios_app'
```

Success response:

```
{
    "status": "ok",
    "user": {
        "id": 9,
        "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8",
        "email": "user@crm.press",
        "confirmed_at": "2021-01-01T10:00:00+01:00", // RFC3339 date or NULL; user confirmation date
        "public_name": "user@crm.press",
        "first_name": "Test",
        "last_name": "User"
    },
    "access": {
        "token": "762eec3fe9f20d87cf865cb40cf6458b" // user token
    }
}
```

Invalid token response:

```
{
    "status": "error",
    "message": "Invalid token"
}
```

---

#### POST `/api/v1/users/get-device-token`

[](#post-apiv1usersget-device-token)

API call generates and returns new device token based on sent `device_id`.

##### *Params:*

[](#params-19)

NameValueRequiredDescriptiondevice\_id*String*yesUser's device id.access\_token*String*noAccess token to pair with generated device token.##### *Example:*

[](#example-25)

```
curl --location --request POST 'http://crm.press:8080/api/v1/users/get-device-token' --form 'device_id=cosijak2'
```

Success response:

```
{
    "device_token": "bfc6191c1837ec3600c23036edf35590"
}
```

---

#### POST `/api/v1/users/set-email-validated`

[](#post-apiv1usersset-email-validated)

API call validates email address for user, if the user exists.

##### *Params:*

[](#params-20)

NameValueRequiredDescriptionEmail*String*yesUser's email address.##### *Example:*

[](#example-26)

```
curl 'http://crm.press/api/v1/users/set-email-validated' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=admin%40admin.sk'
```

Success response:

```
{
    "status": "ok",
    "message": "Email has been validated",
    "code": "success"
}
```

Invalid request response:

```
{
    "status": "error",
    "message": "Details about problem",
    "code": "invalid_request"
}
```

Invalid email response:

```
{
    "status": "error",
    "message": "Email not valid",
    "code": "invalid_param"
}
```

Email not assigned response:

```
{
    "status": "error",
    "message": "Email isn't assigned to any user",
    "code": "email_not_found",
}
```

---

#### POST `/api/v1/users/set-email-invalidated`

[](#post-apiv1usersset-email-invalidated)

API call invalidates email address for user, if the user exists.

##### *Params:*

[](#params-21)

NameValueRequiredDescriptionEmail*String*yesUser's email address.##### *Example:*

[](#example-27)

```
curl 'http://crm.press/api/v1/users/set-email-invalidated' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'email=admin%40admin.sk'
```

Success response:

```
{
    "status": "ok",
    "message": "Email has been invalidated",
    "code": "success"
}
```

All other responses are the same as for /validateMail method above

---

---

#### POST `/api/v2/users/set-email-validated`

[](#post-apiv2usersset-email-validated)

API call validates email addresses for users that exist.

##### *Params:*

[](#params-22)

NameValueRequiredDescriptionemails*String\[\]*yesArray of emails to validate.##### *Example:*

[](#example-28)

```
curl 'http://crm.press/api/v2/users/set-email-validated \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data '{
    "emails": ["john+doe@gmail.com", "name@example.com"]
  }'
```

Success response:

```
{
    "status": "ok",
}
```

---

---

#### POST `/api/v2/users/set-email-invalidated`

[](#post-apiv2usersset-email-invalidated)

API call invalidates email addresses for users that exist.

##### *Params:*

[](#params-23)

NameValueRequiredDescriptionemails*String\[\]*yesArray of emails to invalidate.##### *Example:*

[](#example-29)

```
curl 'http://crm.press/api/v2/users/set-email-invalidated \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data '{
    "emails": ["john+doe@gmail.com", "name@example.com"]
  }'
```

Success response:

```
{
  "status": "ok",
}
```

---

---

#### POST `/api/v1/users/google-token-sign-in`

[](#post-apiv1usersgoogle-token-sign-in)

API for authentication of user using Google Sign-In with ID token, as described in . Endpoint tries to match google user to an existing user using email address. If such user does not exist, a new account is created.

##### *Params:*

[](#params-24)

NameValueRequiredDescriptionid\_token*String*yesGoogle signed JWT token containing user datacreate\_access\_token*Boolean*noIf true, access token for user is createddevice\_token*String*noToken used for pairing mobile device to users access tokens. Can be generated by [`users/get-device-token`](#post-apiv1usersget-device-token). Use only with parameter `create_access_token=true`.gsi\_auth\_code*String*noGoogle auth code used for one-time exchange for credentials. Acquired credentials contain `id_token` which replaces `id_token` sent as parameter.is\_web*Boolean*noSet to `true` if `gsi_auth_code` was retrieved from web surface using JS OAuth library. Defaults to `false`.source*String*noSource of user registration - string label groupping users registered via the same source.locale*String*noUser locale.##### *Example:*

[](#example-30)

```
curl -v –X POST http://crm.press/users/google-token-sign-in \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'id_token=ID_TOKEN_CONTENT&create_access_token=true'
```

Success response:

```
{
    "status": "ok",
    "user": {
        "id": 101,
        "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8",
        "email": "example_user@gmail.com",
        "created_at": "2021-01-01T10:00:00+01:00", // RFC3339 date; user creation date
        "confirmed_at": "2021-01-01T10:00:00+01:00", // RFC3339 date or NULL; user confirmation date
    },
    "user_meta": {
        "key": "value" // String
    },
    "access": {
        "token": "762eec3fe9f20d87cf865cb40cf6458c" // user token
    }
}
```

#### POST `/api/v1/users/apple-token-sign-in`

[](#post-apiv1usersapple-token-sign-in)

API for authentication of user using Apple Sign-In with ID token. Endpoint tries to match google user to an existing user using email address. If such user does not exist, a new account is created.

##### *Params:*

[](#params-25)

NameValueRequiredDescriptionid\_token*String*yesApple signed JWT token containing user datacreate\_access\_token*Boolean*noIf true, access token for user is createddevice\_token*String*noToken used for pairing mobile device to users access tokens. Can be generated by [`users/get-device-token`](#post-apiv1usersget-device-token). Use only with parameter `create_access_token=true`.locale*String*noUser locale.##### *Example:*

[](#example-31)

```
curl -v –X POST http://crm.press/users/apple-token-sign-in \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: application/json' \
  --data 'id_token=ID_TOKEN_CONTENT&create_access_token=true'
```

Success response:

```
{
    "status": "ok",
    "user": {
        "id": 101,
        "uuid": "35e6b53c-340c-4dc3-ad36-f81b2b1f00a8",
        "email": "example_user@gmail.com",
        "created_at": "2021-01-01T10:00:00+01:00", // RFC3339 date; user creation date
        "confirmed_at": "2021-01-01T10:00:00+01:00", // RFC3339 date or NULL; user confirmation date
    },
    "user_meta": {
        "key": "value" // String
    },
    "access": {
        "token": "762eec3fe9f20d87cf865cb40cf6458c" // user token
    }
}
```

#### POST `/api/v1/user/validate-email`

[](#post-apiv1uservalidate-email)

API call validates email against backend email validators.

##### *Params:*

[](#params-26)

NameValueRequiredDescriptionemail*String*yesEmail address that will be validated.##### *Example:*

[](#example-32)

```
curl --location 'http://crm.press:8080/api/v1/users/validate-email' \
--form 'email="info@example.com"'
```

Success response (email is valid):

```
{
  "status": "success"
}
```

Error response (email is not valid):

```
{
  "status": "error",
  "message": "Email 'info@example.com' is not valid",
  "code": "invalid_email"
}
```

Components
----------

[](#components)

**AddressWidget**

Admin user detail address widget.

[![alt text](docs/user_address.png "AddressWidget")](docs/user_address.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/AddressWidget/AddressWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L251)

**AutologinTokens**

Admin user detail tokens widget.

[![alt text](docs/autologin_tokens.png "AutologinTokens")](docs/autologin_tokens.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/AutologinTokens/AutologinTokens.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L204)

**MonthToDateUsersStatWidget**

Admin dashboard single stat widget.

[![alt text](docs/relative_new_users.png "MonthToDateUsersStatWidget")](docs/relative_new_users.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/MonthToDateUsersStatWidget/MonthToDateUsersStatWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L240)

**MonthUsersSmallBarGraphWidget**

Admin users header widget.

[![alt text](docs/small_users_graph.png "MonthUsersSmallBarGraphWidget")](docs/small_users_graph.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/MonthUsersSmallBarGraphWidget/MonthUsersSmallBarGraphWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L245)

**MonthUsersStatWidget**

Admin dashboard single stat widget.

[![alt text](docs/month_new_users.png "MonthUsersStatWidget")](docs/month_new_users.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/MonthUsersStatWidget/MonthUsersStatWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L235)

**ActiveRegisteredUsersStatWidget**

Admin dashboard single stat widget.

[![alt text](docs/active_registered_users.png "ActiveRegisteredUsersStatWidget")](docs/active_registered_users.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/ActiveRegisteredUsersStatWidget/ActiveRegisteredUsersStatWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/20ffe0c6035784412ec732565998caede418a0d1/src/UsersModule.php#L225)

**TodayUsersStatWidget**

Admin dashboard single stat widget.

[![alt text](docs/today_new_users.png "TodayUsersStatWidget")](docs/today_new_users.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/TodayUsersStatWidget/TodayUsersStatWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L230)

**UserActionsLog**

Admin user detail listing component.

[![alt text](docs/user_actions_log.png "UserActionsLog")](docs/user_actions_log.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/UserActionsLog/UserActionLogAdmin.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/presenters/UserActionsLogAdminPresenter.php#L49)

**UserAddressListWidget**

Admin user detail address listing widget with configurable icons and AJAX "Load more" functionality.

[![alt text](docs/user_address_list.png "UserAddressListWidget")](docs/user_address_list.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/master/src/Components/UserAddressListWidget/UserAddressListWidget.php)

[How to use](https://github.com/remp2020/crm-users-module/blob/master/src/UsersModule.php#L352)

Configuration exampleConfigure address type icons and visibility in your module's `config.neon`:

**InvoicesModule:**

```
userAddressListConfig:
    setup:
        - addIconMapping(invoice, fa-file-lines)
        - addAlwaysVisibleTypes(invoice)
```

**PrintModule:**

```
userAddressListConfig:
    setup:
        - addIconMapping(print, fa-truck)
        - addAlwaysVisibleTypes(print)
```

**UsersModule:**

```
userAddressListConfig:
    factory: Crm\UsersModule\Components\UserAddressListWidget\UserAddressListConfig
    setup:
        - addIconMapping(default, fa-envelope)
```

**Available methods:**

- `addIconMapping(string $addressType, string $faIcon)` - Add icon mapping for specific address type.
- `setAlwaysVisibleTypes(string ...$types)` - Set always visible types (replaces existing).
- `addAlwaysVisibleTypes(string ...$addressTypes)` - Add types to always visible list.

**UserLoginAttempts**

Admin user detail listing component.

[![alt text](docs/user_login_attempts.png "UserLoginAttempts")](docs/user_login_attempts.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/UserLoginAttempts/UserLoginAttempts.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L194)

**UserMeta**

Admin user detail listing component.

[![alt text](docs/user_meta.png "UserMeta")](docs/user_meta.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/UserMeta/UserMeta.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L209)

**UserPasswordChanges**

Admin user detail listing component.

[![alt text](docs/password_changes.png "UserPasswordChanges")](docs/password_changes.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/UserPasswordChanges/UserPasswordChanges.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L199)

**UserSourceAccesses**

Admin user detail listing component.

[![alt text](docs/last_accesses.png "UserSourceAccesses")](docs/last_accesses.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/UserSourceAccesses/UserSourceAccesses.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L220)

**UserTokens**

Admin user detail listing component.

[![alt text](docs/user_tokens.png "UserTokens")](docs/user_tokens.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/UserTokens/UserTokens.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/UsersModule.php#L214)

**DetailWidget**

Admin user detail meta widget.

[![alt text](docs/detail_widget.png "DetailWidget")](docs/detail_widget.png)

[Source code](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/components/Widgets/DetailWidget/DetailWidget.php#L1)

[How to use](https://github.com/remp2020/crm-users-module/blob/0683e04c95027043d1d30f1d0aa868d9270e7aaf/src/presenters/UsersAdminPresenter.php#L381)

###  Health Score

53

—

FairBetter than 97% of packages

Maintenance52

Moderate activity, may be stable

Popularity33

Limited adoption so far

Community31

Small or concentrated contributor base

Maturity84

Battle-tested with a long release history

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~29 days

Recently: every ~63 days

Total

84

Last Release

159d ago

Major Versions

0.39.0 → 2.0.02022-08-25

1.2.3 → 2.6.02023-05-02

2.11.0 → 3.0.02024-01-22

2.11.1 → 3.3.12024-08-12

3.6.2 → 4.0.02025-04-02

PHP version history (5 changes)0.3.0PHP ^7.1

0.30.0PHP ^7.3

0.35.0PHP ^7.4

2.0.0PHP ^8.0

3.0.0PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c733f9bd683c3814197d8a532b7da1ba1f631bb1efe1cde5f064feab1e24877?d=identicon)[rootpd](/maintainers/rootpd)

![](https://www.gravatar.com/avatar/a05541c0b35f43d95db5342210e1d9e64242888812d13f5b096df48b6aabd863?d=identicon)[msvitok](/maintainers/msvitok)

![](https://www.gravatar.com/avatar/2c30fdbc85cda35b94f7f59399918193a0289152281abcef344ec9ee82864177?d=identicon)[markoph](/maintainers/markoph)

---

Top Contributors

[![rootpd](https://avatars.githubusercontent.com/u/812909?v=4)](https://github.com/rootpd "rootpd (283 commits)")[![markoph](https://avatars.githubusercontent.com/u/6843562?v=4)](https://github.com/markoph "markoph (199 commits)")[![miroc](https://avatars.githubusercontent.com/u/1230714?v=4)](https://github.com/miroc "miroc (161 commits)")[![tomaj](https://avatars.githubusercontent.com/u/446736?v=4)](https://github.com/tomaj "tomaj (59 commits)")[![Matefko](https://avatars.githubusercontent.com/u/22897457?v=4)](https://github.com/Matefko "Matefko (50 commits)")[![zoldia](https://avatars.githubusercontent.com/u/1526070?v=4)](https://github.com/zoldia "zoldia (39 commits)")[![lubos-michalik](https://avatars.githubusercontent.com/u/63700066?v=4)](https://github.com/lubos-michalik "lubos-michalik (34 commits)")[![burithetech](https://avatars.githubusercontent.com/u/3502143?v=4)](https://github.com/burithetech "burithetech (15 commits)")[![mikoczy](https://avatars.githubusercontent.com/u/14105084?v=4)](https://github.com/mikoczy "mikoczy (7 commits)")[![weblate](https://avatars.githubusercontent.com/u/1607653?v=4)](https://github.com/weblate "weblate (7 commits)")[![davidkvasnovsky](https://avatars.githubusercontent.com/u/12381721?v=4)](https://github.com/davidkvasnovsky "davidkvasnovsky (5 commits)")[![mmacsodi-fcm](https://avatars.githubusercontent.com/u/169140294?v=4)](https://github.com/mmacsodi-fcm "mmacsodi-fcm (4 commits)")[![nakashu](https://avatars.githubusercontent.com/u/1550659?v=4)](https://github.com/nakashu "nakashu (3 commits)")[![ricco24](https://avatars.githubusercontent.com/u/1409647?v=4)](https://github.com/ricco24 "ricco24 (2 commits)")[![Brezak](https://avatars.githubusercontent.com/u/59848927?v=4)](https://github.com/Brezak "Brezak (2 commits)")[![alexmerz](https://avatars.githubusercontent.com/u/1321687?v=4)](https://github.com/alexmerz "alexmerz (1 commits)")[![mattusik](https://avatars.githubusercontent.com/u/1252223?v=4)](https://github.com/mattusik "mattusik (1 commits)")[![mmacsodi](https://avatars.githubusercontent.com/u/21061881?v=4)](https://github.com/mmacsodi "mmacsodi (1 commits)")[![PayteR](https://avatars.githubusercontent.com/u/1248565?v=4)](https://github.com/PayteR "PayteR (1 commits)")[![Martin-Ha](https://avatars.githubusercontent.com/u/10489892?v=4)](https://github.com/Martin-Ha "Martin-Ha (1 commits)")

### Embed Badge

![Health badge](/badges/remp-crm-users-module/health.svg)

```
[![Health](https://phpackages.com/badges/remp-crm-users-module/health.svg)](https://phpackages.com/packages/remp-crm-users-module)
```

###  Alternatives

[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

728272.9k20](/packages/civicrm-civicrm-core)[firefly-iii/data-importer

Firefly III Data Import Tool.

7545.8k](/packages/firefly-iii-data-importer)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
