first commit
This commit is contained in:
34
administrator/components/com_users/access.xml
Normal file
34
administrator/components/com_users/access.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<access component="com_users">
|
||||
<section name="component">
|
||||
<action name="core.admin" title="JACTION_ADMIN" />
|
||||
<action name="core.options" title="JACTION_OPTIONS" />
|
||||
<action name="core.manage" title="JACTION_MANAGE" />
|
||||
<action name="core.create" title="JACTION_CREATE" />
|
||||
<action name="core.delete" title="JACTION_DELETE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="core.edit.state" title="JACTION_EDITSTATE" />
|
||||
<action name="core.edit.value" title="JACTION_EDITVALUE" />
|
||||
</section>
|
||||
<section name="category">
|
||||
<action name="core.create" title="JACTION_CREATE" />
|
||||
<action name="core.delete" title="JACTION_DELETE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="core.edit.state" title="JACTION_EDITSTATE" />
|
||||
<action name="core.edit.own" title="JACTION_EDITOWN" />
|
||||
</section>
|
||||
<section name="fieldgroup">
|
||||
<action name="core.create" title="JACTION_CREATE" />
|
||||
<action name="core.delete" title="JACTION_DELETE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="core.edit.state" title="JACTION_EDITSTATE" />
|
||||
<action name="core.edit.own" title="JACTION_EDITOWN" />
|
||||
<action name="core.edit.value" title="JACTION_EDITVALUE" />
|
||||
</section>
|
||||
<section name="field">
|
||||
<action name="core.delete" title="JACTION_DELETE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="core.edit.state" title="JACTION_EDITSTATE" />
|
||||
<action name="core.edit.value" title="JACTION_EDITVALUE" />
|
||||
</section>
|
||||
</access>
|
||||
441
administrator/components/com_users/config.xml
Normal file
441
administrator/components/com_users/config.xml
Normal file
@ -0,0 +1,441 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<help key="Users:_Options"/>
|
||||
<inlinehelp button="show"/>
|
||||
<fieldset
|
||||
name="user_options"
|
||||
label="COM_USERS_CONFIG_USER_OPTIONS" >
|
||||
<field
|
||||
name="allowUserRegistration"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_ALLOWREGISTRATION_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="new_usertype"
|
||||
type="usergrouplist"
|
||||
label="COM_USERS_CONFIG_FIELD_NEW_USER_TYPE_LABEL"
|
||||
default="2"
|
||||
checksuperusergroup="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="guest_usergroup"
|
||||
type="usergrouplist"
|
||||
label="COM_USERS_CONFIG_FIELD_GUEST_USER_GROUP_LABEL"
|
||||
default="1"
|
||||
checksuperusergroup="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="sendpassword"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_SENDPASSWORD_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="useractivation"
|
||||
type="list"
|
||||
label="COM_USERS_CONFIG_FIELD_USERACTIVATION_LABEL"
|
||||
default="2"
|
||||
validate="options"
|
||||
>
|
||||
<option value="0">JNONE</option>
|
||||
<option value="1">COM_USERS_CONFIG_FIELD_USERACTIVATION_OPTION_SELFACTIVATION</option>
|
||||
<option value="2">COM_USERS_CONFIG_FIELD_USERACTIVATION_OPTION_ADMINACTIVATION</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="mail_to_admin"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_MAILTOADMIN_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="captcha"
|
||||
type="plugins"
|
||||
label="COM_USERS_CONFIG_FIELD_CAPTCHA_LABEL"
|
||||
folder="captcha"
|
||||
filter="cmd"
|
||||
useglobal="true"
|
||||
>
|
||||
<option value="0">JOPTION_DO_NOT_USE</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="frontend_userparams"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_FRONTEND_USERPARAMS_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="site_language"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_FRONTEND_LANG_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
showon="frontend_userparams:1"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="change_login_name"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_CHANGEUSERNAME_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="domain_options"
|
||||
label="COM_USERS_CONFIG_DOMAIN_OPTIONS"
|
||||
description="COM_USERS_CONFIG_FIELD_DOMAINS_DESC"
|
||||
>
|
||||
|
||||
<field
|
||||
name="domains"
|
||||
type="subform"
|
||||
label="COM_USERS_CONFIG_FIELD_DOMAINS_LABEL"
|
||||
hiddenLabel="true"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
formsource="administrator/components/com_users/forms/config_domain.xml"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="password_options"
|
||||
label="COM_USERS_CONFIG_PASSWORD_OPTIONS" >
|
||||
<field
|
||||
name="reset_count"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_COUNT_LABEL"
|
||||
filter="integer"
|
||||
min="0"
|
||||
max="20"
|
||||
step="1"
|
||||
default="10"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="reset_time"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_TIME_LABEL"
|
||||
filter="integer"
|
||||
min="1"
|
||||
max="24"
|
||||
step="1"
|
||||
default="1"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="minimum_length"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_MINIMUM_PASSWORD_LENGTH"
|
||||
filter="integer"
|
||||
min="8"
|
||||
step="1"
|
||||
default="12"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="minimum_integers"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_MINIMUM_INTEGERS"
|
||||
filter="integer"
|
||||
min="0"
|
||||
step="1"
|
||||
default="0"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="minimum_symbols"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_MINIMUM_SYMBOLS"
|
||||
filter="integer"
|
||||
min="0"
|
||||
step="1"
|
||||
default="0"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="minimum_uppercase"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_MINIMUM_UPPERCASE"
|
||||
filter="integer"
|
||||
min="0"
|
||||
step="1"
|
||||
default="0"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="minimum_lowercase"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_FIELD_MINIMUM_LOWERCASE"
|
||||
filter="integer"
|
||||
min="0"
|
||||
step="1"
|
||||
default="0"
|
||||
validate="number"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="multifactorauth"
|
||||
label="COM_USERS_CONFIG_MULTIFACTORAUTH_SETTINGS_LABEL"
|
||||
description="COM_USERS_CONFIG_MULTIFACTORAUTH_SETTINGS_DESC"
|
||||
addfieldprefix="Joomla\Component\Users\Administrator\Field"
|
||||
>
|
||||
<field
|
||||
name="allowed_positions_frontend"
|
||||
type="ModulesPosition"
|
||||
label="COM_USERS_CONFIG_ALLOWED_POSITIONS_FRONTEND_LABEL"
|
||||
description="COM_USERS_CONFIG_ALLOWED_POSITIONS_FRONTEND_DESC"
|
||||
default=""
|
||||
layout="joomla.form.field.list-fancy-select"
|
||||
client="site"
|
||||
multiple="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="frontend_show_title"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FRONTEND_SHOW_TITLE_LABEL"
|
||||
description="COM_USERS_CONFIG_FRONTEND_SHOW_TITLE_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="allowed_positions_backend"
|
||||
type="ModulesPosition"
|
||||
label="COM_USERS_CONFIG_ALLOWED_POSITIONS_BACKEND_LABEL"
|
||||
description="COM_USERS_CONFIG_ALLOWED_POSITIONS_BACKEND_DESC"
|
||||
default=""
|
||||
layout="joomla.form.field.list-fancy-select"
|
||||
client="administrator"
|
||||
multiple="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="neverMFAUserGroups"
|
||||
type="UserGroupList"
|
||||
label="COM_USERS_CONFIG_NEVERMFAUSERGROUPS_LABEL"
|
||||
description="COM_USERS_CONFIG_NEVERMFAUSERGROUPS_DESC"
|
||||
layout="joomla.form.field.list-fancy-select"
|
||||
checksuperusergroup="1"
|
||||
default=""
|
||||
multiple="1"
|
||||
>
|
||||
<option value="0">COM_USERS_CONFIG_LBL_NOGROUP</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="forceMFAUserGroups"
|
||||
type="UserGroupList"
|
||||
label="COM_USERS_CONFIG_FORCEMFAUSERGROUPS_LABEL"
|
||||
description="COM_USERS_CONFIG_FORCEMFAUSERGROUPS_DESC"
|
||||
layout="joomla.form.field.list-fancy-select"
|
||||
checksuperusergroup="1"
|
||||
default=""
|
||||
multiple="1"
|
||||
>
|
||||
<option value="0">COM_USERS_CONFIG_LBL_NOGROUP</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="captive_template"
|
||||
type="templatestyle"
|
||||
label="COM_USERS_CONFIG_FRONTEND_CAPTIVE_TEMPLATE_LABEL"
|
||||
description="COM_USERS_CONFIG_FRONTEND_CAPTIVE_TEMPLATE_DESC"
|
||||
client="site"
|
||||
>
|
||||
<option value="">JOPTION_USE_DEFAULT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="mfaonsilent"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_MFAONSILENT_LABEL"
|
||||
description="COM_USERS_CONFIG_MFAONSILENT_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="silentresponses"
|
||||
type="text"
|
||||
label="COM_USERS_CONFIG_SILENTRESPONSES_LABEL"
|
||||
description="COM_USERS_CONFIG_SILENTRESPONSES_DESC"
|
||||
default="cookie, passwordless"
|
||||
showon="mfaonsilent:0"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="mfaredirectonlogin"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_REDIRECTONLOGIN_LABEL"
|
||||
description="COM_USERS_CONFIG_REDIRECTONLOGIN_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="mfaredirecturl"
|
||||
type="text"
|
||||
label="COM_USERS_CONFIG_REDIRECTURL_LABEL"
|
||||
description="COM_USERS_CONFIG_REDIRECTURL_DESC"
|
||||
default=""
|
||||
showon="mfaredirectonlogin:1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="mfatrycount"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_MFATRYCOUNT_LABEL"
|
||||
filter="integer"
|
||||
min="0"
|
||||
max="20"
|
||||
step="1"
|
||||
default="10"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="mfatrytime"
|
||||
type="number"
|
||||
label="COM_USERS_CONFIG_MFATRYTIME_LABEL"
|
||||
filter="integer"
|
||||
min="1"
|
||||
max="24"
|
||||
step="1"
|
||||
default="1"
|
||||
validate="number"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="user_notes_history"
|
||||
label="COM_USERS_CONFIG_FIELD_NOTES_HISTORY" >
|
||||
|
||||
<field
|
||||
name="save_history"
|
||||
type="radio"
|
||||
label="JGLOBAL_SAVE_HISTORY_OPTIONS_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="history_limit"
|
||||
type="number"
|
||||
label="JGLOBAL_HISTORY_LIMIT_OPTIONS_LABEL"
|
||||
filter="integer"
|
||||
default="5"
|
||||
showon="save_history:1"
|
||||
/>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="massmail"
|
||||
label="COM_USERS_MASS_MAIL"
|
||||
description="COM_USERS_MASS_MAIL_DESC">
|
||||
|
||||
<field
|
||||
name="mailSubjectPrefix"
|
||||
type="text"
|
||||
label="COM_USERS_CONFIG_FIELD_SUBJECT_PREFIX_LABEL"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="mailBodySuffix"
|
||||
type="textarea"
|
||||
label="COM_USERS_CONFIG_FIELD_MAILBODY_SUFFIX_LABEL"
|
||||
rows="5"
|
||||
cols="30"
|
||||
/>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="integration"
|
||||
label="JGLOBAL_INTEGRATION_LABEL"
|
||||
description="COM_USERS_CONFIG_INTEGRATION_SETTINGS_DESC"
|
||||
>
|
||||
<fieldset name="integration_customfields"
|
||||
label="JGLOBAL_FIELDS_TITLE"
|
||||
>
|
||||
<field
|
||||
name="custom_fields_enable"
|
||||
type="radio"
|
||||
label="JGLOBAL_CUSTOM_FIELDS_ENABLE_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="permissions"
|
||||
label="JCONFIG_PERMISSIONS_LABEL"
|
||||
>
|
||||
|
||||
<field
|
||||
name="rules"
|
||||
type="rules"
|
||||
label="JCONFIG_PERMISSIONS_LABEL"
|
||||
filter="rules"
|
||||
validate="rules"
|
||||
component="com_users"
|
||||
section="component"
|
||||
/>
|
||||
|
||||
</fieldset>
|
||||
</config>
|
||||
5
administrator/components/com_users/forms/category.xml
Normal file
5
administrator/components/com_users/forms/category.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<help key="User_Notes:_New_or_Edit_Category" />
|
||||
<listhelp key="User_Notes:_Categories" />
|
||||
</form>
|
||||
27
administrator/components/com_users/forms/config_domain.xml
Normal file
27
administrator/components/com_users/forms/config_domain.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fieldset>
|
||||
<field
|
||||
name="name"
|
||||
type="text"
|
||||
label="COM_USERS_CONFIG_FIELD_DOMAIN_NAME_LABEL"
|
||||
description="COM_USERS_CONFIG_FIELD_DOMAIN_NAME_DESC"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="rule"
|
||||
type="radio"
|
||||
label="COM_USERS_CONFIG_FIELD_DOMAIN_RULE_LABEL"
|
||||
description="COM_USERS_CONFIG_FIELD_DOMAIN_RULE_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
required="true"
|
||||
default="0"
|
||||
filter="integer"
|
||||
validate="options"
|
||||
>
|
||||
<option value="0">COM_USERS_CONFIG_FIELD_DOMAIN_RULE_OPTION_DISALLOW</option>
|
||||
<option value="1">COM_USERS_CONFIG_FIELD_DOMAIN_RULE_OPTION_ALLOW</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</form>
|
||||
12
administrator/components/com_users/forms/fields/user.xml
Normal file
12
administrator/components/com_users/forms/fields/user.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fields name="params" label="COM_FIELDS_FIELD_BASIC_LABEL">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="display"
|
||||
type="hidden"
|
||||
default="2"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</form>
|
||||
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form addfieldprefix="Joomla\Component\Users\Administrator\Field">
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
label="COM_USERS_SEARCH_ASSETS"
|
||||
description="COM_USERS_SEARCH_IN_ASSETS"
|
||||
hint="JSEARCH_FILTER"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="component"
|
||||
type="Components"
|
||||
label="COM_USERS_OPTION_LABEL_COMPONENT"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_SELECT_COMPONENT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="level_start"
|
||||
type="Levels"
|
||||
label="COM_USERS_OPTION_LABEL_LEVEL_START"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_SELECT_LEVEL_START</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="level_end"
|
||||
type="Levels"
|
||||
label="COM_USERS_OPTION_LABEL_LEVEL_END"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_SELECT_LEVEL_END</option>
|
||||
</field>
|
||||
</fields>
|
||||
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
class="js-select-submit-on-change"
|
||||
default="a.lft ASC"
|
||||
validate="options"
|
||||
>
|
||||
<option value="">JGLOBAL_SORT_BY</option>
|
||||
<option value="a.title ASC">COM_USERS_HEADING_ASSET_TITLE_ASC</option>
|
||||
<option value="a.title DESC">COM_USERS_HEADING_ASSET_TITLE_DESC</option>
|
||||
<option value="a.name ASC">COM_USERS_HEADING_ASSET_NAME_ASC</option>
|
||||
<option value="a.name DESC">COM_USERS_HEADING_ASSET_NAME_DESC</option>
|
||||
<option value="a.lft ASC">COM_USERS_HEADING_LFT_ASC</option>
|
||||
<option value="a.lft DESC">COM_USERS_HEADING_LFT_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
class="js-select-submit-on-change"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form addfieldprefix="Joomla\Component\Users\Administrator\Field">
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
label="COM_USERS_SEARCH_ASSETS"
|
||||
description="COM_USERS_SEARCH_IN_ASSETS"
|
||||
hint="JSEARCH_FILTER"
|
||||
/>
|
||||
<field
|
||||
name="component"
|
||||
type="Components"
|
||||
label="COM_USERS_OPTION_LABEL_COMPONENT"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_SELECT_COMPONENT</option>
|
||||
</field>
|
||||
<field
|
||||
name="level_start"
|
||||
type="Levels"
|
||||
label="COM_USERS_OPTION_LABEL_LEVEL_START"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_SELECT_LEVEL_START</option>
|
||||
</field>
|
||||
<field
|
||||
name="level_end"
|
||||
type="Levels"
|
||||
label="COM_USERS_OPTION_LABEL_LEVEL_END"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_SELECT_LEVEL_END</option>
|
||||
</field>
|
||||
</fields>
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
class="js-select-submit-on-change"
|
||||
default="a.lft ASC"
|
||||
validate="options"
|
||||
>
|
||||
<option value="">JGLOBAL_SORT_BY</option>
|
||||
<option value="a.title ASC">COM_USERS_HEADING_ASSET_TITLE_ASC</option>
|
||||
<option value="a.title DESC">COM_USERS_HEADING_ASSET_TITLE_DESC</option>
|
||||
<option value="a.name ASC">COM_USERS_HEADING_ASSET_NAME_ASC</option>
|
||||
<option value="a.name DESC">COM_USERS_HEADING_ASSET_NAME_DESC</option>
|
||||
<option value="a.lft ASC">COM_USERS_HEADING_LFT_ASC</option>
|
||||
<option value="a.lft DESC">COM_USERS_HEADING_LFT_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
class="js-select-submit-on-change"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
38
administrator/components/com_users/forms/filter_groups.xml
Normal file
38
administrator/components/com_users/forms/filter_groups.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
label="COM_USERS_SEARCH_GROUPS_LABEL"
|
||||
description="COM_USERS_SEARCH_IN_GROUPS"
|
||||
hint="JSEARCH_FILTER"
|
||||
/>
|
||||
</fields>
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
class="js-select-submit-on-change"
|
||||
default="a.lft ASC"
|
||||
validate="options"
|
||||
>
|
||||
<option value="">JGLOBAL_SORT_BY</option>
|
||||
<option value="a.lft ASC">JGRID_HEADING_ORDERING_ASC</option>
|
||||
<option value="a.lft DESC">JGRID_HEADING_ORDERING_DESC</option>
|
||||
<option value="a.title ASC">COM_USERS_HEADING_GROUP_TITLE_ASC</option>
|
||||
<option value="a.title DESC">COM_USERS_HEADING_GROUP_TITLE_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
class="js-select-submit-on-change"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
38
administrator/components/com_users/forms/filter_levels.xml
Normal file
38
administrator/components/com_users/forms/filter_levels.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
label="COM_USERS_SEARCH_ACCESS_LEVELS"
|
||||
description="COM_USERS_SEARCH_IN_LEVEL_NAME"
|
||||
hint="JSEARCH_FILTER"
|
||||
/>
|
||||
</fields>
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
class="js-select-submit-on-change"
|
||||
default="a.ordering ASC"
|
||||
validate="options"
|
||||
>
|
||||
<option value="">JGLOBAL_SORT_BY</option>
|
||||
<option value="a.ordering ASC">JGRID_HEADING_ORDERING_ASC</option>
|
||||
<option value="a.ordering DESC">JGRID_HEADING_ORDERING_DESC</option>
|
||||
<option value="a.title ASC">COM_USERS_HEADING_LEVEL_NAME_ASC</option>
|
||||
<option value="a.title DESC">COM_USERS_HEADING_LEVEL_NAME_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
class="js-select-submit-on-change"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
73
administrator/components/com_users/forms/filter_notes.xml
Normal file
73
administrator/components/com_users/forms/filter_notes.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
label="COM_USERS_SEARCH_USER_NOTES"
|
||||
description="COM_USERS_SEARCH_IN_NOTE_TITLE"
|
||||
hint="JSEARCH_FILTER"
|
||||
/>
|
||||
<field
|
||||
name="published"
|
||||
type="status"
|
||||
label="JSTATUS"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">JOPTION_SELECT_PUBLISHED</option>
|
||||
</field>
|
||||
<field
|
||||
name="category_id"
|
||||
type="category"
|
||||
label="JCATEGORY"
|
||||
extension="com_users"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">JOPTION_SELECT_CATEGORY</option>
|
||||
</field>
|
||||
<field
|
||||
name="level"
|
||||
type="integer"
|
||||
label="JGLOBAL_MAXLEVEL_LABEL"
|
||||
first="1"
|
||||
last="10"
|
||||
step="1"
|
||||
languages="*"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">JOPTION_SELECT_MAX_LEVELS</option>
|
||||
</field>
|
||||
</fields>
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
class="js-select-submit-on-change"
|
||||
default="a.review_time DESC"
|
||||
validate="options"
|
||||
>
|
||||
<option value="">JGLOBAL_SORT_BY</option>
|
||||
<option value="a.state ASC">JSTATUS_ASC</option>
|
||||
<option value="a.state DESC">JSTATUS_DESC</option>
|
||||
<option value="a.subject ASC">COM_USERS_HEADING_SUBJECT_ASC</option>
|
||||
<option value="a.subject DESC">COM_USERS_HEADING_SUBJECT_DESC</option>
|
||||
<option value="c.title ASC">COM_USERS_HEADING_CATEGORY_ASC</option>
|
||||
<option value="c.title DESC">COM_USERS_HEADING_CATEGORY_DESC</option>
|
||||
<option value="u.name ASC">COM_USERS_HEADING_USER_ASC</option>
|
||||
<option value="u.name DESC">COM_USERS_HEADING_USER_DESC</option>
|
||||
<option value="a.review_time ASC">COM_USERS_HEADING_REVIEW_ASC</option>
|
||||
<option value="a.review_time DESC">COM_USERS_HEADING_REVIEW_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
class="js-select-submit-on-change"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
98
administrator/components/com_users/forms/filter_users.xml
Normal file
98
administrator/components/com_users/forms/filter_users.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
label="COM_USERS_SEARCH_USERS"
|
||||
description="COM_USERS_SEARCH_IN_NAME"
|
||||
hint="JSEARCH_FILTER"
|
||||
/>
|
||||
<field
|
||||
name="state"
|
||||
type="userstate"
|
||||
label="JSTATUS"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_FILTER_STATE</option>
|
||||
</field>
|
||||
<field
|
||||
name="mfa"
|
||||
type="list"
|
||||
label="COM_USERS_HEADING_MFA"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_FILTER_MFA</option>
|
||||
<option value="1">JENABLED</option>
|
||||
<option value="0">JDISABLED</option>
|
||||
</field>
|
||||
<field
|
||||
name="active"
|
||||
type="useractive"
|
||||
label="COM_USERS_HEADING_ACTIVATED"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_FILTER_ACTIVE</option>
|
||||
</field>
|
||||
<field
|
||||
name="group_id"
|
||||
type="usergrouplist"
|
||||
label="COM_USERS_HEADING_GROUPS"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_FILTER_USERGROUP</option>
|
||||
</field>
|
||||
<field
|
||||
name="lastvisitrange"
|
||||
type="lastvisitdaterange"
|
||||
label="COM_USERS_HEADING_LAST_VISIT_DATE"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_FILTER_LAST_VISIT_DATE</option>
|
||||
</field>
|
||||
<field
|
||||
name="range"
|
||||
type="registrationdaterange"
|
||||
label="COM_USERS_HEADING_REGISTRATION_DATE"
|
||||
class="js-select-submit-on-change"
|
||||
>
|
||||
<option value="">COM_USERS_OPTION_FILTER_DATE</option>
|
||||
</field>
|
||||
</fields>
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
class="js-select-submit-on-change"
|
||||
default="a.name ASC"
|
||||
validate="options"
|
||||
>
|
||||
<option value="">JGLOBAL_SORT_BY</option>
|
||||
<option value="a.name ASC">JGLOBAL_NAME_ASC</option>
|
||||
<option value="a.name DESC">JGLOBAL_NAME_DESC</option>
|
||||
<option value="a.username ASC">COM_USERS_HEADING_USERNAME_ASC</option>
|
||||
<option value="a.username DESC">COM_USERS_HEADING_USERNAME_DESC</option>
|
||||
<option value="a.block ASC">COM_USERS_HEADING_ENABLED_ASC</option>
|
||||
<option value="a.block DESC">COM_USERS_HEADING_ENABLED_DESC</option>
|
||||
<option value="a.activation ASC">COM_USERS_HEADING_ACTIVATED_ASC</option>
|
||||
<option value="a.activation DESC">COM_USERS_HEADING_ACTIVATED_DESC</option>
|
||||
<option value="a.email ASC">COM_USERS_HEADING_EMAIL_ASC</option>
|
||||
<option value="a.email DESC">COM_USERS_HEADING_EMAIL_DESC</option>
|
||||
<option value="a.lastvisitDate ASC">COM_USERS_HEADING_LAST_VISIT_DATE_ASC</option>
|
||||
<option value="a.lastvisitDate DESC">COM_USERS_HEADING_LAST_VISIT_DATE_DESC</option>
|
||||
<option value="a.registerDate ASC">COM_USERS_HEADING_REGISTRATION_DATE_ASC</option>
|
||||
<option value="a.registerDate DESC">COM_USERS_HEADING_REGISTRATION_DATE_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
class="js-select-submit-on-change"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
45
administrator/components/com_users/forms/group.xml
Normal file
45
administrator/components/com_users/forms/group.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form addfieldprefix="Joomla\Component\Users\Administrator\Field">
|
||||
<fieldset name="group_details">
|
||||
<field
|
||||
name="id"
|
||||
type="hidden"
|
||||
default="0"
|
||||
readonly="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="title"
|
||||
type="text"
|
||||
label="COM_USERS_GROUP_FIELD_TITLE_LABEL"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="parent_id"
|
||||
type="groupparent"
|
||||
label="COM_USERS_GROUP_FIELD_PARENT_LABEL"
|
||||
validate="options"
|
||||
>
|
||||
<option value="0" disabled="disabled">COM_USERS_GROUP_FIELD_PARENT_SELECT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="actions"
|
||||
type="hidden"
|
||||
multiple="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="lft"
|
||||
type="hidden"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="rgt"
|
||||
type="hidden"
|
||||
filter="unset"
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
33
administrator/components/com_users/forms/level.xml
Normal file
33
administrator/components/com_users/forms/level.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fieldset>
|
||||
<field
|
||||
name="id"
|
||||
type="hidden"
|
||||
default="0"
|
||||
readonly="true"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="title"
|
||||
type="text"
|
||||
label="COM_USERS_LEVEL_FIELD_TITLE_LABEL"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="ordering"
|
||||
type="text"
|
||||
label="JFIELD_ORDERING_LABEL"
|
||||
description="JFIELD_ORDERING_DESC"
|
||||
default="0"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="rules"
|
||||
type="hidden"
|
||||
filter="intarray"
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
61
administrator/components/com_users/forms/mail.xml
Normal file
61
administrator/components/com_users/forms/mail.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fieldset>
|
||||
|
||||
<field
|
||||
name="recurse"
|
||||
type="checkbox"
|
||||
label="COM_USERS_MAIL_FIELD_RECURSE_LABEL"
|
||||
value="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="mode"
|
||||
type="checkbox"
|
||||
label="COM_USERS_MAIL_FIELD_SEND_IN_HTML_MODE_LABEL"
|
||||
value="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="disabled"
|
||||
type="checkbox"
|
||||
label="COM_USERS_MAIL_FIELD_EMAIL_DISABLED_USERS_LABEL"
|
||||
value="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="group"
|
||||
type="usergrouplist"
|
||||
label="COM_USERS_MAIL_FIELD_GROUP_LABEL"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">COM_USERS_MAIL_FIELD_VALUE_ALL_USERS_GROUPS</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="bcc"
|
||||
type="checkbox"
|
||||
label="COM_USERS_MAIL_FIELD_SEND_AS_BLIND_CARBON_COPY_LABEL"
|
||||
default="1"
|
||||
value="1"
|
||||
checked="1"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="subject"
|
||||
type="text"
|
||||
label="COM_USERS_MAIL_FIELD_SUBJECT_LABEL"
|
||||
maxlength="150"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="message"
|
||||
type="textarea"
|
||||
label="COM_USERS_MAIL_FIELD_MESSAGE_LABEL"
|
||||
cols="70"
|
||||
rows="20"
|
||||
required="true"
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
136
administrator/components/com_users/forms/note.xml
Normal file
136
administrator/components/com_users/forms/note.xml
Normal file
@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fieldset addfieldprefix="Joomla\Component\Categories\Administrator\Field">
|
||||
<field
|
||||
name="id"
|
||||
type="hidden"
|
||||
label="COM_USERS_FIELD_ID_LABEL"
|
||||
class="readonly"
|
||||
default="0"
|
||||
readonly="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="user_id"
|
||||
type="user"
|
||||
label="COM_USERS_FIELD_USER_ID_LABEL"
|
||||
required="true"
|
||||
validate="UserId"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="catid"
|
||||
type="modal_category"
|
||||
label="COM_USERS_FIELD_CATEGORY_ID_LABEL"
|
||||
extension="com_users"
|
||||
required="true"
|
||||
select="true"
|
||||
new="true"
|
||||
edit="true"
|
||||
clear="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="subject"
|
||||
type="text"
|
||||
label="COM_USERS_FIELD_SUBJECT_LABEL"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="body"
|
||||
type="editor"
|
||||
label="COM_USERS_FIELD_NOTEBODY_LABEL"
|
||||
rows="10"
|
||||
cols="80"
|
||||
filter="safehtml"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="state"
|
||||
type="list"
|
||||
label="JSTATUS"
|
||||
class="form-select-color-state"
|
||||
default="1"
|
||||
validate="options"
|
||||
>
|
||||
<option value="1">JPUBLISHED</option>
|
||||
<option value="0">JUNPUBLISHED</option>
|
||||
<option value="2">JARCHIVED</option>
|
||||
<option value="-2">JTRASHED</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="review_time"
|
||||
type="calendar"
|
||||
label="COM_USERS_FIELD_REVIEW_TIME_LABEL"
|
||||
default="NOW"
|
||||
translateformat="true"
|
||||
filter="user_utc"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="checked_out"
|
||||
type="hidden"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="checked_out_time"
|
||||
type="hidden"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="created_user_id"
|
||||
type="hidden"
|
||||
label="JGLOBAL_FIELD_CREATED_BY_LABEL"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="created_time"
|
||||
type="hidden"
|
||||
label="JGLOBAL_FIELD_CREATED_LABEL"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="modified_user_id"
|
||||
type="hidden"
|
||||
label="JGLOBAL_FIELD_MODIFIED_BY_LABEL"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="modified_time"
|
||||
type="hidden"
|
||||
label="JGLOBAL_FIELD_MODIFIED_LABEL"
|
||||
filter="unset"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="publish_up"
|
||||
type="calendar"
|
||||
label="JGLOBAL_FIELD_PUBLISH_UP_LABEL"
|
||||
translateformat="true"
|
||||
showtime="true"
|
||||
filter="user_utc"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="publish_down"
|
||||
type="calendar"
|
||||
label="JGLOBAL_FIELD_PUBLISH_DOWN_LABEL"
|
||||
translateformat="true"
|
||||
showtime="true"
|
||||
filter="user_utc"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="version_note"
|
||||
type="text"
|
||||
label="JGLOBAL_FIELD_VERSION_NOTE_LABEL"
|
||||
maxlength="255"
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
239
administrator/components/com_users/forms/user.xml
Normal file
239
administrator/components/com_users/forms/user.xml
Normal file
@ -0,0 +1,239 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form addfieldprefix="Joomla\Component\Users\Administrator\Field">
|
||||
<fieldset name="user_details" label="COM_USERS_USER_ACCOUNT_DETAILS">
|
||||
<field
|
||||
name="name"
|
||||
type="text"
|
||||
label="COM_USERS_USER_FIELD_NAME_LABEL"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="username"
|
||||
type="text"
|
||||
label="COM_USERS_USER_FIELD_USERNAME_LABEL"
|
||||
required="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="password"
|
||||
type="password"
|
||||
label="JGLOBAL_PASSWORD"
|
||||
rules="true"
|
||||
autocomplete="new-password"
|
||||
class="validate-password-strength"
|
||||
filter="raw"
|
||||
validate="password"
|
||||
strengthmeter="true"
|
||||
force="on"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="password2"
|
||||
type="password"
|
||||
label="COM_USERS_USER_FIELD_PASSWORD2_LABEL"
|
||||
autocomplete="new-password"
|
||||
class="validate-passwordExtra"
|
||||
filter="raw"
|
||||
message="COM_USERS_USER_FIELD_PASSWORD1_MESSAGE"
|
||||
validate="equals"
|
||||
field="password"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="email"
|
||||
type="email"
|
||||
label="JGLOBAL_EMAIL"
|
||||
required="true"
|
||||
validate="email"
|
||||
validDomains="com_users.domains"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="registerDate"
|
||||
type="calendar"
|
||||
label="COM_USERS_USER_FIELD_REGISTERDATE_LABEL"
|
||||
class="readonly"
|
||||
readonly="true"
|
||||
translateformat="true"
|
||||
showtime="true"
|
||||
filter="user_utc"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="lastvisitDate"
|
||||
type="calendar"
|
||||
label="COM_USERS_USER_FIELD_LASTVISIT_LABEL"
|
||||
class="readonly"
|
||||
readonly="true"
|
||||
translateformat="true"
|
||||
showtime="true"
|
||||
filter="user_utc"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="lastResetTime"
|
||||
type="calendar"
|
||||
label="COM_USERS_USER_FIELD_LASTRESET_LABEL"
|
||||
class="readonly"
|
||||
readonly="true"
|
||||
translateformat="true"
|
||||
showtime="true"
|
||||
filter="user_utc"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="resetCount"
|
||||
type="number"
|
||||
label="COM_USERS_USER_FIELD_RESETCOUNT_LABEL"
|
||||
class="readonly"
|
||||
default="0"
|
||||
readonly="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="sendEmail"
|
||||
type="radio"
|
||||
label="COM_USERS_USER_FIELD_SENDEMAIL_LABEL"
|
||||
default="0"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="block"
|
||||
type="radio"
|
||||
label="COM_USERS_USER_FIELD_BLOCK_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="1">COM_USERS_USER_FIELD_BLOCK</option>
|
||||
<option value="0">COM_USERS_USER_FIELD_ENABLE</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="requireReset"
|
||||
type="radio"
|
||||
label="COM_USERS_USER_FIELD_REQUIRERESET_LABEL"
|
||||
default="0"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="id"
|
||||
type="text"
|
||||
label="JGLOBAL_FIELD_ID_LABEL"
|
||||
class="readonly"
|
||||
default="0"
|
||||
readonly="true"
|
||||
/>
|
||||
|
||||
</fieldset>
|
||||
<field name="groups" type="hidden" />
|
||||
|
||||
<fields name="params">
|
||||
|
||||
<!-- Basic user account settings. -->
|
||||
<fieldset name="settings" label="COM_USERS_SETTINGS_FIELDSET_LABEL">
|
||||
|
||||
<field
|
||||
name="admin_style"
|
||||
type="templatestyle"
|
||||
label="COM_USERS_USER_FIELD_BACKEND_TEMPLATE_LABEL"
|
||||
client="administrator"
|
||||
filter="uint"
|
||||
>
|
||||
<option value="">JOPTION_USE_DEFAULT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="admin_language"
|
||||
type="language"
|
||||
label="COM_USERS_USER_FIELD_BACKEND_LANGUAGE_LABEL"
|
||||
client="administrator"
|
||||
>
|
||||
<option value="">JOPTION_USE_DEFAULT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="language"
|
||||
type="language"
|
||||
label="COM_USERS_USER_FIELD_FRONTEND_LANGUAGE_LABEL"
|
||||
client="site"
|
||||
>
|
||||
<option value="">JOPTION_USE_DEFAULT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="editor"
|
||||
type="plugins"
|
||||
label="COM_USERS_USER_FIELD_EDITOR_LABEL"
|
||||
folder="editors"
|
||||
>
|
||||
<option value="">JOPTION_USE_DEFAULT</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="timezone"
|
||||
type="timezone"
|
||||
label="COM_USERS_USER_FIELD_TIMEZONE_LABEL"
|
||||
>
|
||||
<option value="">JOPTION_USE_DEFAULT</option>
|
||||
</field>
|
||||
|
||||
</fieldset>
|
||||
<!-- User accessibility settings -->
|
||||
<fieldset
|
||||
name="accessibility"
|
||||
label="COM_USERS_A11Y_SETTINGS_FIELDSET_LABEL"
|
||||
description="COM_USERS_A11Y_SETTINGS_FIELDSET_DESC"
|
||||
>
|
||||
<field
|
||||
name="a11y_mono"
|
||||
type="radio"
|
||||
label="COM_USERS_A11Y_SETTINGS_FIELD_MONOCHROME"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="monochrome">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="a11y_contrast"
|
||||
type="radio"
|
||||
label="COM_USERS_A11Y_SETTINGS_FIELD_CONTRAST"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="high_contrast">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="a11y_highlight"
|
||||
type="radio"
|
||||
label="COM_USERS_A11Y_SETTINGS_FIELD_HIGHLIGHT"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="highlight">JYES</option>
|
||||
</field>
|
||||
<field
|
||||
name="a11y_font"
|
||||
type="radio"
|
||||
label="COM_USERS_A11Y_SETTINGS_FIELD_FONTSIZE"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="fontsize">JYES</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
</fields>
|
||||
</form>
|
||||
29
administrator/components/com_users/helpers/debug.php
Normal file
29
administrator/components/com_users/helpers/debug.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
*/
|
||||
|
||||
use Joomla\Component\Users\Administrator\Helper\DebugHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users component debugging helper.
|
||||
*
|
||||
* @since 1.6
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use \Joomla\Component\Users\Administrator\Helper\DebugHelper instead
|
||||
*/
|
||||
class UsersHelperDebug extends DebugHelper
|
||||
{
|
||||
}
|
||||
27
administrator/components/com_users/helpers/users.php
Normal file
27
administrator/components/com_users/helpers/users.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
*/
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users component helper.
|
||||
*
|
||||
* @since 1.6
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use \Joomla\Component\Users\Administrator\Helper\UsersHelper instead
|
||||
*/
|
||||
class UsersHelper extends \Joomla\Component\Users\Administrator\Helper\UsersHelper
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Post-installation message about the new Multi-factor Authentication: condition check.
|
||||
*
|
||||
* Returns true if neither of the two new core MFA plugins are enabled.
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
function com_users_postinstall_mfa_condition(): bool
|
||||
{
|
||||
return \count(PluginHelper::getPlugin('multifactorauth')) < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-installation message about the new Multi-factor Authentication: action.
|
||||
*
|
||||
* Enables the core MFA plugins.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
function com_users_postinstall_mfa_action(): void
|
||||
{
|
||||
/** @var DatabaseInterface $db */
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$coreMfaPlugins = ['email', 'totp', 'webauthn', 'yubikey'];
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('enabled') . ' = 1')
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('multifactorauth'))
|
||||
->whereIn($db->quoteName('element'), $coreMfaPlugins, ParameterType::STRING);
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
|
||||
$url = 'index.php?option=com_plugins&filter[folder]=multifactorauth';
|
||||
Factory::getApplication()->redirect($url);
|
||||
}
|
||||
86
administrator/components/com_users/presets/users.xml
Normal file
86
administrator/components/com_users/presets/users.xml
Normal file
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0"?>
|
||||
<menu
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="urn:joomla.org"
|
||||
xsi:schemaLocation="urn:joomla.org menu.xsd"
|
||||
>
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_USERS"
|
||||
type="heading"
|
||||
icon="users"
|
||||
class="class:users"
|
||||
>
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_USER_MANAGER"
|
||||
type="component"
|
||||
element="com_users"
|
||||
link="index.php?option=com_users&view=users"
|
||||
quicktask="index.php?option=com_users&task=user.add"
|
||||
quicktask-title="COM_USERS_MENUS_ADD_USER"
|
||||
/>
|
||||
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_GROUPS"
|
||||
type="component"
|
||||
element="com_users"
|
||||
link="index.php?option=com_users&view=groups"
|
||||
/>
|
||||
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_LEVELS"
|
||||
type="component"
|
||||
element="com_users"
|
||||
link="index.php?option=com_users&view=levels"
|
||||
/>
|
||||
</menuitem>
|
||||
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_FIELDS"
|
||||
type="heading"
|
||||
icon="user-tag"
|
||||
class="class:users"
|
||||
>
|
||||
<menuitem
|
||||
title="MOD_MENU_FIELDS"
|
||||
type="component"
|
||||
element="com_fields"
|
||||
link="index.php?option=com_fields&view=fields&context=com_users.user"
|
||||
/>
|
||||
|
||||
<menuitem
|
||||
title="MOD_MENU_FIELDS_GROUP"
|
||||
type="component"
|
||||
element="com_fields"
|
||||
link="index.php?option=com_fields&view=groups&context=com_users.user"
|
||||
/>
|
||||
</menuitem>
|
||||
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_MISC"
|
||||
type="heading"
|
||||
icon="user-edit"
|
||||
class="class:users"
|
||||
>
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_NOTES"
|
||||
type="component"
|
||||
element="com_users"
|
||||
link="index.php?option=com_users&view=notes"
|
||||
/>
|
||||
|
||||
<menuitem
|
||||
title="COM_USERS_MENUS_NOTE_CATEGORIES"
|
||||
type="component"
|
||||
element="com_categories"
|
||||
link="index.php?option=com_categories&view=categories&extension=com_users"
|
||||
/>
|
||||
|
||||
<menuitem
|
||||
title="MOD_MENU_MASS_MAIL_USERS"
|
||||
type="component"
|
||||
element="com_users"
|
||||
link="index.php?option=com_users&view=mail"
|
||||
scope="massmail"
|
||||
/>
|
||||
</menuitem>
|
||||
</menu>
|
||||
58
administrator/components/com_users/services/provider.php
Normal file
58
administrator/components/com_users/services/provider.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Component\Router\RouterFactoryInterface;
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
|
||||
use Joomla\CMS\Extension\ComponentInterface;
|
||||
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
|
||||
use Joomla\CMS\HTML\Registry;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\Component\Users\Administrator\Extension\UsersComponent;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
|
||||
/**
|
||||
* The users service provider.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Users'));
|
||||
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Users'));
|
||||
$container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Users'));
|
||||
|
||||
$container->set(
|
||||
ComponentInterface::class,
|
||||
function (Container $container) {
|
||||
$component = new UsersComponent($container->get(ComponentDispatcherFactoryInterface::class));
|
||||
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
|
||||
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
|
||||
$component->setRegistry($container->get(Registry::class));
|
||||
|
||||
return $component;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Event\MultiFactor\Callback;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Input\Input;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Multi-factor Authentication plugins' AJAX callback controller
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class CallbackController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param array $config Plugin configuration
|
||||
* @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
|
||||
* @param CMSApplication|null $app CMS application object
|
||||
* @param Input|null $input Joomla CMS input object
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
|
||||
{
|
||||
parent::__construct($config, $factory, $app, $input);
|
||||
|
||||
$this->registerDefaultTask('callback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement a callback feature, typically used for OAuth2 authentication
|
||||
*
|
||||
* @param bool $cachable Can this view be cached
|
||||
* @param array|bool $urlparams An array of safe url parameters and their variable types.
|
||||
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function callback($cachable = false, $urlparams = false): void
|
||||
{
|
||||
$app = $this->app;
|
||||
|
||||
// Get the Method and make sure it's non-empty
|
||||
$method = $this->input->getCmd('method', '');
|
||||
|
||||
if (empty($method)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
PluginHelper::importPlugin('multifactorauth');
|
||||
|
||||
$event = new Callback($method);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
/**
|
||||
* The first plugin to handle the request should either redirect or close the application. If we are still here
|
||||
* no plugin handled the request successfully. Show an error.
|
||||
*/
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Event\MultiFactor\NotifyActionLog;
|
||||
use Joomla\CMS\Event\MultiFactor\Validate;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\UserFactoryAwareInterface;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
|
||||
use Joomla\Component\Users\Administrator\Model\CaptiveModel;
|
||||
use Joomla\Input\Input;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Captive Multi-factor Authentication page controller
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class CaptiveController extends BaseController implements UserFactoryAwareInterface
|
||||
{
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param array $config Plugin configuration
|
||||
* @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
|
||||
* @param CMSApplication|null $app CMS application object
|
||||
* @param Input|null $input Joomla CMS input object
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
|
||||
{
|
||||
parent::__construct($config, $factory, $app, $input);
|
||||
|
||||
$this->registerTask('captive', 'display');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the captive login page
|
||||
*
|
||||
* @param boolean $cachable Ignored. This page is never cached.
|
||||
* @param boolean|array $urlparams Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function display($cachable = false, $urlparams = false): void
|
||||
{
|
||||
$user = $this->app->getIdentity() ?: $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
// Only allow logged in Users
|
||||
if ($user->guest) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
// Get the view object
|
||||
$viewLayout = $this->input->get('layout', 'default', 'string');
|
||||
$view = $this->getView(
|
||||
'Captive',
|
||||
'html',
|
||||
'',
|
||||
[
|
||||
'base_path' => $this->basePath,
|
||||
'layout' => $viewLayout,
|
||||
]
|
||||
);
|
||||
|
||||
$view->document = $this->app->getDocument();
|
||||
|
||||
// If we're already logged in go to the site's home page
|
||||
if ((int) $this->app->getSession()->get('com_users.mfa_checked', 0) === 1) {
|
||||
$url = Route::_('index.php?option=com_users&task=methods.display', false);
|
||||
|
||||
$this->setRedirect($url);
|
||||
}
|
||||
|
||||
// Pass the model to the view
|
||||
/** @var CaptiveModel $model */
|
||||
$model = $this->getModel('Captive');
|
||||
$view->setModel($model, true);
|
||||
|
||||
/** @var BackupcodesModel $codesModel */
|
||||
$codesModel = $this->getModel('Backupcodes');
|
||||
$view->setModel($codesModel, false);
|
||||
|
||||
try {
|
||||
// Suppress all modules on the page except those explicitly allowed
|
||||
$model->suppressAllModules();
|
||||
} catch (\Exception $e) {
|
||||
// If we can't kill the modules we can still survive.
|
||||
}
|
||||
|
||||
// Pass the MFA record ID to the model
|
||||
$recordId = $this->input->getInt('record_id', null);
|
||||
$model->setState('record_id', $recordId);
|
||||
|
||||
// Do not go through $this->display() because it overrides the model.
|
||||
$view->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the MFA code entered by the user
|
||||
*
|
||||
* @param bool $cachable Ignored. This page is never cached.
|
||||
* @param array $urlparameters Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function validate($cachable = false, $urlparameters = [])
|
||||
{
|
||||
// CSRF Check
|
||||
$this->checkToken($this->input->getMethod());
|
||||
|
||||
// Get the MFA parameters from the request
|
||||
$recordId = $this->input->getInt('record_id', null);
|
||||
$code = $this->input->get('code', null, 'raw');
|
||||
/** @var CaptiveModel $model */
|
||||
$model = $this->getModel('Captive');
|
||||
|
||||
// Validate the MFA record
|
||||
$model->setState('record_id', $recordId);
|
||||
$record = $model->getRecord();
|
||||
|
||||
if (empty($record)) {
|
||||
$event = new NotifyActionLog('onComUsersCaptiveValidateInvalidMethod');
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
throw new \RuntimeException(Text::_('COM_USERS_MFA_INVALID_METHOD'), 500);
|
||||
}
|
||||
|
||||
if (!$model->checkTryLimit($record)) {
|
||||
// The try limit is reached, show error and return
|
||||
$captiveURL = Route::_('index.php?option=com_users&view=captive&task=select', false);
|
||||
$message = Text::_('COM_USERS_MFA_TRY_LIMIT_REACHED');
|
||||
$this->setRedirect($captiveURL, $message, 'error');
|
||||
|
||||
$event = new NotifyActionLog('onComUsersCaptiveValidateTryLimitReached');
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the code
|
||||
$user = $this->app->getIdentity() ?: $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
$event = new Validate($record, $user, $code);
|
||||
$results = $this->app
|
||||
->getDispatcher()
|
||||
->dispatch($event->getName(), $event)
|
||||
->getArgument('result', []);
|
||||
|
||||
$isValidCode = false;
|
||||
|
||||
if ($record->method === 'backupcodes') {
|
||||
/** @var BackupcodesModel $codesModel */
|
||||
$codesModel = $this->getModel('Backupcodes');
|
||||
$results = [$codesModel->isBackupCode($code, $user)];
|
||||
/**
|
||||
* This is required! Do not remove!
|
||||
*
|
||||
* There is a store() call below. It saves the in-memory MFA record to the database. That includes the
|
||||
* options key which contains the configuration of the Method. For backup codes, these are the actual codes
|
||||
* you can use. When we check for a backup code validity we also "burn" it, i.e. we remove it from the
|
||||
* options table and save that to the database. However, this DOES NOT update the $record here. Therefore
|
||||
* the call to saveRecord() would overwrite the database contents with a record that _includes_ the backup
|
||||
* code we had just burned. As a result the single use backup codes end up being multiple use.
|
||||
*
|
||||
* By doing a getRecord() here, right after we have "burned" any correct backup codes, we resolve this
|
||||
* issue. The loaded record will reflect the database contents where the options DO NOT include the code we
|
||||
* just used. Therefore the call to store() will result in the correct database state, i.e. the used backup
|
||||
* code being removed.
|
||||
*/
|
||||
$record = $model->getRecord();
|
||||
}
|
||||
|
||||
$isValidCode = array_reduce(
|
||||
$results,
|
||||
function (bool $carry, $result) {
|
||||
return $carry || \boolval($result);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
if (!$isValidCode) {
|
||||
// The code is wrong. Display an error and go back.
|
||||
$captiveURL = Route::_('index.php?option=com_users&view=captive&record_id=' . $recordId, false);
|
||||
$message = Text::_('COM_USERS_MFA_INVALID_CODE');
|
||||
$this->setRedirect($captiveURL, $message, 'error');
|
||||
|
||||
$event = new NotifyActionLog('onComUsersCaptiveValidateFailed', [$record->title]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Last Used, UA and IP columns
|
||||
$jNow = Date::getInstance();
|
||||
|
||||
$record->last_used = $jNow->toSql();
|
||||
$record->tries = 0;
|
||||
$record->last_try = null;
|
||||
$record->store();
|
||||
|
||||
// Flag the user as fully logged in
|
||||
$session = $this->app->getSession();
|
||||
$session->set('com_users.mfa_checked', 1);
|
||||
$session->set('com_users.mandatory_mfa_setup', 0);
|
||||
|
||||
// Get the return URL stored by the plugin in the session
|
||||
$returnUrl = $session->get('com_users.return_url', '');
|
||||
|
||||
// If the return URL is not set or not internal to this site redirect to the site's front page
|
||||
if (empty($returnUrl) || !Uri::isInternal($returnUrl)) {
|
||||
$returnUrl = Uri::base();
|
||||
}
|
||||
|
||||
$this->setRedirect($returnUrl);
|
||||
|
||||
$event = new NotifyActionLog('onComUsersCaptiveValidateSuccess', [$record->title]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Access\Exception\NotAllowed;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users display controller.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class DisplayController extends BaseController
|
||||
{
|
||||
/**
|
||||
* The default view.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $default_view = 'users';
|
||||
|
||||
/**
|
||||
* Checks whether a user can see this view.
|
||||
*
|
||||
* @param string $view The view name.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function canView($view)
|
||||
{
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
|
||||
switch ($view) {
|
||||
// Special permissions.
|
||||
case 'groups':
|
||||
case 'group':
|
||||
case 'levels':
|
||||
case 'level':
|
||||
return $canDo->get('core.admin');
|
||||
|
||||
// Default permissions.
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to display a view.
|
||||
*
|
||||
* @param boolean $cachable If true, the view output will be cached
|
||||
* @param array $urlparams An array of safe URL parameters and their variable types.
|
||||
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
|
||||
*
|
||||
* @return BaseController|boolean This object to support chaining or false on failure.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function display($cachable = false, $urlparams = [])
|
||||
{
|
||||
$view = $this->input->get('view', 'users');
|
||||
$layout = $this->input->get('layout', 'default');
|
||||
$id = $this->input->getInt('id');
|
||||
|
||||
if (!$this->canView($view)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
// Check for edit form.
|
||||
if ($view === 'user' && $layout === 'edit' && !$this->checkEditId('com_users.edit.user', $id)) {
|
||||
// Somehow the person just went to the form - we don't allow that.
|
||||
if (!\count($this->app->getMessageQueue())) {
|
||||
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_users&view=users', false));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($view === 'group' && $layout === 'edit' && !$this->checkEditId('com_users.edit.group', $id)) {
|
||||
// Somehow the person just went to the form - we don't allow that.
|
||||
if (!\count($this->app->getMessageQueue())) {
|
||||
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_users&view=groups', false));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($view === 'level' && $layout === 'edit' && !$this->checkEditId('com_users.edit.level', $id)) {
|
||||
// Somehow the person just went to the form - we don't allow that.
|
||||
if (!\count($this->app->getMessageQueue())) {
|
||||
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_users&view=levels', false));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($view === 'note' && $layout === 'edit' && !$this->checkEditId('com_users.edit.note', $id)) {
|
||||
// Somehow the person just went to the form - we don't allow that.
|
||||
if (!\count($this->app->getMessageQueue())) {
|
||||
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_users&view=notes', false));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\in_array($view, ['captive', 'callback', 'methods', 'method'])) {
|
||||
$controller = $this->factory->createController($view, 'Administrator', [], $this->app, $this->input);
|
||||
$task = $this->input->get('task', '');
|
||||
|
||||
return $controller->execute($task);
|
||||
}
|
||||
|
||||
return parent::display($cachable, $urlparams);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\MVC\Controller\FormController;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User view level controller class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class GroupController extends FormController
|
||||
{
|
||||
/**
|
||||
* @var string The prefix to use with controller messages.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_GROUP';
|
||||
|
||||
/**
|
||||
* Method to check if you can save a new or existing record.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\FormController::allowSave to check the core.admin permission.
|
||||
*
|
||||
* @param array $data An array of input data.
|
||||
* @param string $key The name of the key for the primary key.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function allowSave($data, $key = 'id')
|
||||
{
|
||||
return ($this->app->getIdentity()->authorise('core.admin', $this->option) && parent::allowSave($data, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Joomla\CMS\MVC\Controller\FormController::allowEdit
|
||||
*
|
||||
* Checks that non-Super Admins are not editing Super Admins.
|
||||
*
|
||||
* @param array $data An array of input data.
|
||||
* @param string $key The name of the key for the primary key.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function allowEdit($data = [], $key = 'id')
|
||||
{
|
||||
// Check if this group is a Super Admin
|
||||
if (Access::checkGroup($data[$key], 'core.admin')) {
|
||||
// If I'm not a Super Admin, then disallow the edit.
|
||||
if (!$this->app->getIdentity()->authorise('core.admin')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::allowEdit($data, $key);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Access\Exception\NotAllowed;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\AdminController;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User groups list controller class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class GroupsController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @var string The prefix to use with controller messages.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_GROUPS';
|
||||
|
||||
/**
|
||||
* Proxy for getModel.
|
||||
*
|
||||
* @param string $name The model name. Optional.
|
||||
* @param string $prefix The class prefix. Optional.
|
||||
* @param array $config Configuration array for model. Optional.
|
||||
*
|
||||
* @return object The model.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getModel($name = 'Group', $prefix = 'Administrator', $config = ['ignore_request' => true])
|
||||
{
|
||||
return parent::getModel($name, $prefix, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\AdminController::delete to check the core.admin permission.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
parent::delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to publish a list of records.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\AdminController::publish to check the core.admin permission.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function publish()
|
||||
{
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
parent::publish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the order of one or more records.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\AdminController::reorder to check the core.admin permission.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function reorder()
|
||||
{
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
return parent::reorder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to save the submitted ordering values for records.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\AdminController::saveorder to check the core.admin permission.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function saveorder()
|
||||
{
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
return parent::saveorder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in of one or more records.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\AdminController::checkin to check the core.admin permission.
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function checkin()
|
||||
{
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
return parent::checkin();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Access\Exception\NotAllowed;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\FormController;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User view level controller class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class LevelController extends FormController
|
||||
{
|
||||
/**
|
||||
* @var string The prefix to use with controller messages.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_LEVEL';
|
||||
|
||||
/**
|
||||
* Method to check if you can save a new or existing record.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\FormController::allowSave to check the core.admin permission.
|
||||
*
|
||||
* @param array $data An array of input data.
|
||||
* @param string $key The name of the key for the primary key.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function allowSave($data, $key = 'id')
|
||||
{
|
||||
return ($this->app->getIdentity()->authorise('core.admin', $this->option) && parent::allowSave($data, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides JControllerForm::allowEdit
|
||||
*
|
||||
* Checks that non-Super Admins are not editing Super Admins.
|
||||
*
|
||||
* @param array $data An array of input data.
|
||||
* @param string $key The name of the key for the primary key.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.8.8
|
||||
*/
|
||||
protected function allowEdit($data = [], $key = 'id')
|
||||
{
|
||||
// Check for if Super Admin can edit
|
||||
$viewLevel = $this->getModel('Level', 'Administrator')->getItem((int) $data['id']);
|
||||
|
||||
// If this group is super admin and this user is not super admin, canEdit is false
|
||||
if (!$this->app->getIdentity()->authorise('core.admin') && $viewLevel->rules && Access::checkGroup($viewLevel->rules[0], 'core.admin')) {
|
||||
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
|
||||
|
||||
$this->setRedirect(
|
||||
Route::_(
|
||||
'index.php?option=' . $this->option . '&view=' . $this->view_list
|
||||
. $this->getRedirectToListAppend(),
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::allowEdit($data, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item.
|
||||
*
|
||||
* Overrides Joomla\CMS\MVC\Controller\FormController::delete to check the core.admin permission.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
// Check for request forgeries.
|
||||
$this->checkToken();
|
||||
|
||||
$ids = (array) $this->input->get('cid', [], 'int');
|
||||
|
||||
// Remove zero values resulting from input filter
|
||||
$ids = array_filter($ids);
|
||||
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->setMessage(Text::_('COM_USERS_NO_LEVELS_SELECTED'), 'warning');
|
||||
} else {
|
||||
// Get the model.
|
||||
$model = $this->getModel();
|
||||
|
||||
// Remove the items.
|
||||
if ($model->delete($ids)) {
|
||||
$this->setMessage(Text::plural('COM_USERS_N_LEVELS_DELETED', \count($ids)));
|
||||
}
|
||||
}
|
||||
|
||||
$this->setRedirect('index.php?option=com_users&view=levels');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\AdminController;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User view levels list controller class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class LevelsController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @var string The prefix to use with controller messages.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_LEVELS';
|
||||
|
||||
/**
|
||||
* Proxy for getModel.
|
||||
*
|
||||
* @param string $name The model name. Optional.
|
||||
* @param string $prefix The class prefix. Optional.
|
||||
* @param array $config Configuration array for model. Optional.
|
||||
*
|
||||
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getModel($name = 'Level', $prefix = 'Administrator', $config = ['ignore_request' => true])
|
||||
{
|
||||
return parent::getModel($name, $prefix, $config);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users mail controller.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class MailController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Send the mail
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
// Redirect to admin index if mass mailer disabled in conf
|
||||
if ($this->app->get('massmailoff', 0) == 1) {
|
||||
$this->app->redirect(Route::_('index.php', false));
|
||||
}
|
||||
|
||||
// Check for request forgeries.
|
||||
$this->checkToken('request');
|
||||
|
||||
$model = $this->getModel('Mail');
|
||||
|
||||
if ($model->send()) {
|
||||
$type = 'message';
|
||||
} else {
|
||||
$type = 'error';
|
||||
}
|
||||
|
||||
$msg = $model->getError();
|
||||
$this->setRedirect('index.php?option=com_users&view=mail', $msg, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the mail
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
// Check for request forgeries.
|
||||
$this->checkToken('request');
|
||||
|
||||
// Clear data from session.
|
||||
$this->app->setUserState('com_users.display.mail.data', null);
|
||||
|
||||
$this->setRedirect('index.php?option=com_users&view=users');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,486 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Event\MultiFactor\NotifyActionLog;
|
||||
use Joomla\CMS\Event\MultiFactor\SaveSetup;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\BaseController as BaseControllerAlias;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\User\UserFactoryAwareInterface;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
|
||||
use Joomla\Component\Users\Administrator\Model\MethodModel;
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
use Joomla\Input\Input;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Multi-factor Authentication method controller
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class MethodController extends BaseControllerAlias implements UserFactoryAwareInterface
|
||||
{
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param array $config Plugin configuration
|
||||
* @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
|
||||
* @param CMSApplication|null $app CMS application object
|
||||
* @param Input|null $input Joomla CMS input object
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
|
||||
{
|
||||
// We have to tell Joomla what is the name of the view, otherwise it defaults to the name of the *component*.
|
||||
$config['default_view'] = 'method';
|
||||
$config['default_task'] = 'add';
|
||||
|
||||
parent::__construct($config, $factory, $app, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task by triggering a Method in the derived class.
|
||||
*
|
||||
* @param string $task The task to perform. If no matching task is found, the '__default' task is executed, if
|
||||
* defined.
|
||||
*
|
||||
* @return mixed The value returned by the called Method.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function execute($task)
|
||||
{
|
||||
if (empty($task) || $task === 'display') {
|
||||
$task = 'add';
|
||||
}
|
||||
|
||||
return parent::execute($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new MFA Method
|
||||
*
|
||||
* @param boolean $cachable Ignored. This page is never cached.
|
||||
* @param boolean|array $urlparams Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function add($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
$this->assertCanEdit($user);
|
||||
|
||||
// Also make sure the Method really does exist
|
||||
$method = $this->input->getCmd('method');
|
||||
$this->assertMethodExists($method);
|
||||
|
||||
/** @var MethodModel $model */
|
||||
$model = $this->getModel('Method');
|
||||
$model->setState('method', $method);
|
||||
|
||||
// Pass the return URL to the view
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
$viewLayout = $this->input->get('layout', 'default', 'string');
|
||||
$view = $this->getView('Method', 'html');
|
||||
$view->setLayout($viewLayout);
|
||||
$view->returnURL = $returnURL;
|
||||
$view->user = $user;
|
||||
$view->document = $this->app->getDocument();
|
||||
|
||||
$view->setModel($model, true);
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodBeforeAdd', [$user, $method]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
$view->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an existing MFA Method
|
||||
*
|
||||
* @param boolean $cachable Ignored. This page is never cached.
|
||||
* @param boolean|array $urlparams Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function edit($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
$this->assertCanEdit($user);
|
||||
|
||||
// Also make sure the Method really does exist
|
||||
$id = $this->input->getInt('id');
|
||||
$record = $this->assertValidRecordId($id, $user);
|
||||
|
||||
if ($id <= 0) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
/** @var MethodModel $model */
|
||||
$model = $this->getModel('Method');
|
||||
$model->setState('id', $id);
|
||||
|
||||
// Pass the return URL to the view
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
$viewLayout = $this->input->get('layout', 'default', 'string');
|
||||
$view = $this->getView('Method', 'html');
|
||||
$view->setLayout($viewLayout);
|
||||
$view->returnURL = $returnURL;
|
||||
$view->user = $user;
|
||||
$view->document = $this->app->getDocument();
|
||||
|
||||
$view->setModel($model, true);
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodBeforeEdit', [$id, $user]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
$view->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate backup codes
|
||||
*
|
||||
* @param boolean $cachable Ignored. This page is never cached.
|
||||
* @param boolean|array $urlparams Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function regenerateBackupCodes($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
$this->checkToken($this->input->getMethod());
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
$this->assertCanEdit($user);
|
||||
|
||||
/** @var BackupcodesModel $model */
|
||||
$model = $this->getModel('Backupcodes');
|
||||
$model->regenerateBackupCodes($user);
|
||||
|
||||
$backupCodesRecord = $model->getBackupCodesRecord($user);
|
||||
|
||||
// Redirect
|
||||
$redirectUrl = 'index.php?option=com_users&task=method.edit&user_id=' . $userId . '&id=' . $backupCodesRecord->id;
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
|
||||
if (!empty($returnURL) && Uri::isInternal(base64_decode($returnURL))) {
|
||||
$redirectUrl .= '&returnurl=' . $returnURL;
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_($redirectUrl, false));
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodAfterRegenerateBackupCodes');
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing MFA Method
|
||||
*
|
||||
* @param boolean $cachable Ignored. This page is never cached.
|
||||
* @param boolean|array $urlparams Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function delete($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
$this->checkToken($this->input->getMethod());
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
$this->assertCanDelete($user);
|
||||
|
||||
// Also make sure the Method really does exist
|
||||
$id = $this->input->getInt('id');
|
||||
$record = $this->assertValidRecordId($id, $user);
|
||||
|
||||
if ($id <= 0) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
$type = null;
|
||||
$message = null;
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodBeforeDelete', [$id, $user]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
try {
|
||||
$record->delete();
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
$type = 'error';
|
||||
}
|
||||
|
||||
// Redirect
|
||||
$url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
|
||||
if (!empty($returnURL) && Uri::isInternal(base64_decode($returnURL))) {
|
||||
$url = base64_decode($returnURL);
|
||||
}
|
||||
|
||||
$this->setRedirect($url, $message, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the MFA Method
|
||||
*
|
||||
* @param boolean $cachable Ignored. This page is never cached.
|
||||
* @param boolean|array $urlparams Ignored. This page is never cached.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function save($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
$this->checkToken($this->input->getMethod());
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
$this->assertCanEdit($user);
|
||||
|
||||
// Redirect
|
||||
$url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
|
||||
if (!empty($returnURL) && Uri::isInternal(base64_decode($returnURL))) {
|
||||
$url = base64_decode($returnURL);
|
||||
}
|
||||
|
||||
// The record must either be new (ID zero) or exist
|
||||
$id = $this->input->getInt('id', 0);
|
||||
$record = $this->assertValidRecordId($id, $user);
|
||||
|
||||
// If it's a new record we need to read the Method from the request and update the (not yet created) record.
|
||||
if ($record->id == 0) {
|
||||
$methodName = $this->input->getCmd('method');
|
||||
$this->assertMethodExists($methodName);
|
||||
$record->method = $methodName;
|
||||
}
|
||||
|
||||
/** @var MethodModel $model */
|
||||
$model = $this->getModel('Method');
|
||||
|
||||
// Ask the plugin to validate the input by calling onUserMultifactorSaveSetup
|
||||
$result = [];
|
||||
$input = $this->app->getInput();
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodBeforeSave', [$id, $user]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
try {
|
||||
$event = new SaveSetup($record, $input);
|
||||
$pluginResults = $this->app
|
||||
->getDispatcher()
|
||||
->dispatch($event->getName(), $event)
|
||||
->getArgument('result', []);
|
||||
|
||||
foreach ($pluginResults as $pluginResult) {
|
||||
$result = array_merge($result, $pluginResult);
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
// Go back to the edit page
|
||||
$nonSefUrl = 'index.php?option=com_users&task=method.';
|
||||
|
||||
if ($id) {
|
||||
$nonSefUrl .= 'edit&id=' . (int) $id;
|
||||
} else {
|
||||
$nonSefUrl .= 'add&method=' . $record->method;
|
||||
}
|
||||
|
||||
$nonSefUrl .= '&user_id=' . $userId;
|
||||
|
||||
if (!empty($returnURL)) {
|
||||
$nonSefUrl .= '&returnurl=' . urlencode($returnURL);
|
||||
}
|
||||
|
||||
$url = Route::_($nonSefUrl, false);
|
||||
$this->setRedirect($url, $e->getMessage(), 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the record's options with the plugin response
|
||||
$title = $this->input->getString('title', null);
|
||||
$title = trim($title);
|
||||
|
||||
if (empty($title)) {
|
||||
$method = $model->getMethod($record->method);
|
||||
$title = $method['display'];
|
||||
}
|
||||
|
||||
// Update the record's "default" flag
|
||||
$default = $this->input->getBool('default', false);
|
||||
$record->title = $title;
|
||||
$record->options = $result;
|
||||
$record->default = $default ? 1 : 0;
|
||||
|
||||
// Ask the model to save the record
|
||||
$saved = $record->store();
|
||||
|
||||
if (!$saved) {
|
||||
// Go back to the edit page
|
||||
$nonSefUrl = 'index.php?option=com_users&task=method.';
|
||||
|
||||
if ($id) {
|
||||
$nonSefUrl .= 'edit&id=' . (int) $id;
|
||||
} else {
|
||||
$nonSefUrl .= 'add';
|
||||
}
|
||||
|
||||
$nonSefUrl .= '&user_id=' . $userId;
|
||||
|
||||
if (!empty($returnURL)) {
|
||||
$nonSefUrl .= '&returnurl=' . urlencode($returnURL);
|
||||
}
|
||||
|
||||
$url = Route::_($nonSefUrl, false);
|
||||
$this->setRedirect($url, $record->getError(), 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setRedirect($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the provided ID is a valid record identified for the given user
|
||||
*
|
||||
* @param int $id Record ID to check
|
||||
* @param User|null $user User record. Null to use current user.
|
||||
*
|
||||
* @return MfaTable The loaded record
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function assertValidRecordId($id, ?User $user = null): MfaTable
|
||||
{
|
||||
if (\is_null($user)) {
|
||||
$user = $this->app->getIdentity() ?: $this->getUserFactory()->loadUserById(0);
|
||||
}
|
||||
|
||||
/** @var MethodModel $model */
|
||||
$model = $this->getModel('Method');
|
||||
|
||||
$model->setState('id', $id);
|
||||
|
||||
$record = $model->getRecord($user);
|
||||
|
||||
if (\is_null($record) || ($record->id != $id) || ($record->user_id != $user->id)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the user can add / edit MFA methods.
|
||||
*
|
||||
* @param User|null $user User record. Null to use current user.
|
||||
*
|
||||
* @return void
|
||||
* @throws \RuntimeException|\Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function assertCanEdit(?User $user = null): void
|
||||
{
|
||||
if (!MfaHelper::canAddEditMethod($user)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the user can delete MFA records / disable MFA.
|
||||
*
|
||||
* @param User|null $user User record. Null to use current user.
|
||||
*
|
||||
* @return void
|
||||
* @throws \RuntimeException|\Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function assertCanDelete(?User $user = null): void
|
||||
{
|
||||
if (!MfaHelper::canDeleteMethod($user)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the specified MFA Method exists, is activated and enabled for the current user
|
||||
*
|
||||
* @param string|null $method The Method to check
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function assertMethodExists(?string $method): void
|
||||
{
|
||||
/** @var MethodModel $model */
|
||||
$model = $this->getModel('Method');
|
||||
|
||||
if (empty($method) || !$model->methodExists($method)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that there is a logged in user.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function assertLoggedInUser(): void
|
||||
{
|
||||
$user = $this->app->getIdentity() ?: $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
if ($user->guest) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Event\MultiFactor\NotifyActionLog;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\UserFactoryAwareInterface;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Model\MethodsModel;
|
||||
use Joomla\Input\Input;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Multi-factor Authentication methods selection and management controller
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class MethodsController extends BaseController implements UserFactoryAwareInterface
|
||||
{
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param array $config Plugin configuration
|
||||
* @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
|
||||
* @param CMSApplication|null $app CMS application object
|
||||
* @param Input|null $input Joomla CMS input object
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
|
||||
{
|
||||
// We have to tell Joomla what is the name of the view, otherwise it defaults to the name of the *component*.
|
||||
$config['default_view'] = 'Methods';
|
||||
|
||||
parent::__construct($config, $factory, $app, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Multi-factor Authentication for the current user
|
||||
*
|
||||
* @param bool $cachable Can this view be cached
|
||||
* @param array $urlparams An array of safe url parameters and their variable types.
|
||||
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function disable($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
$this->checkToken($this->input->getMethod());
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = ($userId === null)
|
||||
? $this->app->getIdentity()
|
||||
: $this->getUserFactory()->loadUserById($userId);
|
||||
$user = $user ?? $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
if (!MfaHelper::canDeleteMethod($user)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
// Delete all MFA Methods for the user
|
||||
/** @var MethodsModel $model */
|
||||
$model = $this->getModel('Methods');
|
||||
$type = null;
|
||||
$message = null;
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodsBeforeDisable', [$user]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
try {
|
||||
$model->deleteAll($user);
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
$type = 'error';
|
||||
}
|
||||
|
||||
// Redirect
|
||||
$url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
|
||||
if (!empty($returnURL) && Uri::isInternal(base64_decode($returnURL))) {
|
||||
$url = base64_decode($returnURL);
|
||||
}
|
||||
|
||||
$this->setRedirect($url, $message, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available Multi-factor Authentication Methods available and guide the user to setting them up
|
||||
*
|
||||
* @param bool $cachable Can this view be cached
|
||||
* @param array $urlparams An array of safe url parameters and their variable types.
|
||||
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function display($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = ($userId === null)
|
||||
? $this->app->getIdentity()
|
||||
: $this->getUserFactory()->loadUserById($userId);
|
||||
$user = $user ?? $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
if (!MfaHelper::canShowConfigurationInterface($user)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
$viewLayout = $this->input->get('layout', 'default', 'string');
|
||||
$view = $this->getView('Methods', 'html');
|
||||
$view->setLayout($viewLayout);
|
||||
$view->returnURL = $returnURL;
|
||||
$view->user = $user;
|
||||
$view->document = $this->app->getDocument();
|
||||
|
||||
$methodsModel = $this->getModel('Methods');
|
||||
$view->setModel($methodsModel, true);
|
||||
|
||||
$backupCodesModel = $this->getModel('Backupcodes');
|
||||
$view->setModel($backupCodesModel, false);
|
||||
|
||||
$view->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Multi-factor Authentication for the current user
|
||||
*
|
||||
* @param bool $cachable Can this view be cached
|
||||
* @param array $urlparams An array of safe url parameters and their variable types.
|
||||
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function doNotShowThisAgain($cachable = false, $urlparams = []): void
|
||||
{
|
||||
$this->assertLoggedInUser();
|
||||
|
||||
$this->checkToken($this->input->getMethod());
|
||||
|
||||
// Make sure I am allowed to edit the specified user
|
||||
$userId = $this->input->getInt('user_id', null);
|
||||
$user = ($userId === null)
|
||||
? $this->app->getIdentity()
|
||||
: $this->getUserFactory()->loadUserById($userId);
|
||||
$user = $user ?? $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
if (!MfaHelper::canAddEditMethod($user)) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
$event = new NotifyActionLog('onComUsersControllerMethodsBeforeDoNotShowThisAgain', [$user]);
|
||||
$this->app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
/** @var MethodsModel $model */
|
||||
$model = $this->getModel('Methods');
|
||||
$model->setFlag($user, true);
|
||||
|
||||
// Redirect
|
||||
$url = Uri::base();
|
||||
$returnURL = $this->input->getBase64('returnurl');
|
||||
|
||||
if (!empty($returnURL) && Uri::isInternal(base64_decode($returnURL))) {
|
||||
$url = base64_decode($returnURL);
|
||||
}
|
||||
|
||||
$this->setRedirect($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that there is a user currently logged in
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function assertLoggedInUser(): void
|
||||
{
|
||||
$user = $this->app->getIdentity() ?: $this->getUserFactory()->loadUserById(0);
|
||||
|
||||
if ($user->guest) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\FormController;
|
||||
use Joomla\CMS\Versioning\VersionableControllerTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User note controller class.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class NoteController extends FormController
|
||||
{
|
||||
use VersionableControllerTrait;
|
||||
|
||||
/**
|
||||
* The prefix to use with controller messages.
|
||||
*
|
||||
* @var string
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_NOTE';
|
||||
|
||||
/**
|
||||
* Gets the URL arguments to append to an item redirect.
|
||||
*
|
||||
* @param integer $recordId The primary key id for the item.
|
||||
* @param string $key The name of the primary key variable.
|
||||
*
|
||||
* @return string The arguments to append to the redirect URL.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
protected function getRedirectToItemAppend($recordId = null, $key = 'id')
|
||||
{
|
||||
$append = parent::getRedirectToItemAppend($recordId, $key);
|
||||
|
||||
$userId = $this->input->get('u_id', 0, 'int');
|
||||
|
||||
if ($userId) {
|
||||
$append .= '&u_id=' . $userId;
|
||||
}
|
||||
|
||||
return $append;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\AdminController;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User notes controller class.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class NotesController extends AdminController
|
||||
{
|
||||
/**
|
||||
* The prefix to use with controller messages.
|
||||
*
|
||||
* @var string
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_NOTES';
|
||||
|
||||
/**
|
||||
* Method to get a model object, loading it if required.
|
||||
*
|
||||
* @param string $name The model name. Optional.
|
||||
* @param string $prefix The class prefix. Optional.
|
||||
* @param array $config Configuration array for model. Optional.
|
||||
*
|
||||
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function getModel($name = 'Note', $prefix = 'Administrator', $config = ['ignore_request' => true])
|
||||
{
|
||||
return parent::getModel($name, $prefix, $config);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\MVC\Controller\FormController;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User controller class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class UserController extends FormController
|
||||
{
|
||||
/**
|
||||
* @var string The prefix to use with controller messages.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_USER';
|
||||
|
||||
/**
|
||||
* Overrides Joomla\CMS\MVC\Controller\FormController::allowEdit
|
||||
*
|
||||
* Checks that non-Super Admins are not editing Super Admins.
|
||||
*
|
||||
* @param array $data An array of input data.
|
||||
* @param string $key The name of the key for the primary key.
|
||||
*
|
||||
* @return boolean True if allowed, false otherwise.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function allowEdit($data = [], $key = 'id')
|
||||
{
|
||||
// Check if this person is a Super Admin
|
||||
if (Access::check($data[$key], 'core.admin')) {
|
||||
// If I'm not a Super Admin, then disallow the edit.
|
||||
if (!$this->app->getIdentity()->authorise('core.admin')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow users to edit their own account
|
||||
if (isset($data[$key]) && (int) $this->app->getIdentity()->id === (int) $data[$key]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::allowEdit($data, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent cancel to redirect when using status edit account.
|
||||
*
|
||||
* @param string $key The name of the primary key of the URL variable.
|
||||
*
|
||||
* @return boolean True if access level checks pass, false otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function cancel($key = null)
|
||||
{
|
||||
$result = parent::cancel();
|
||||
|
||||
if ($return = $this->input->get('return', '', 'BASE64')) {
|
||||
$return = base64_decode($return);
|
||||
|
||||
// Don't redirect to an external URL.
|
||||
if (!Uri::isInternal($return)) {
|
||||
$return = Uri::base();
|
||||
}
|
||||
|
||||
$this->app->redirect($return);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent save to redirect when using status edit account.
|
||||
*
|
||||
* @param string $key The name of the primary key of the URL variable.
|
||||
* @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
|
||||
*
|
||||
* @return boolean True if successful, false otherwise.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function save($key = null, $urlVar = null)
|
||||
{
|
||||
$result = parent::save($key, $urlVar);
|
||||
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($task === 'save' && $return = $this->input->get('return', '', 'BASE64')) {
|
||||
$return = base64_decode($return);
|
||||
|
||||
// Don't redirect to an external URL.
|
||||
if (!Uri::isInternal($return)) {
|
||||
$return = Uri::base();
|
||||
}
|
||||
|
||||
$this->setRedirect($return);
|
||||
}
|
||||
|
||||
// If a user has to renew a password but has no permission for users
|
||||
if (!$this->app->getIdentity()->authorise('core.admin', 'com_users')) {
|
||||
$this->setRedirect('index.php');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to run batch operations.
|
||||
*
|
||||
* @param object $model The model.
|
||||
*
|
||||
* @return boolean True on success, false on failure
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function batch($model = null)
|
||||
{
|
||||
$this->checkToken();
|
||||
|
||||
// Set the model
|
||||
$model = $this->getModel('User', 'Administrator', []);
|
||||
|
||||
// Preset the redirect
|
||||
$this->setRedirect(Route::_('index.php?option=com_users&view=users' . $this->getRedirectToListAppend(), false));
|
||||
|
||||
return parent::batch($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that allows child controller access to model data after the data has been saved.
|
||||
*
|
||||
* @param BaseDatabaseModel $model The data model object.
|
||||
* @param array $validData The validated data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
protected function postSaveHook(BaseDatabaseModel $model, $validData = [])
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Controller;
|
||||
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Input\Input;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Controller\AdminController;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Response\JsonResponse;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users list controller class.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class UsersController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @var string The prefix to use with controller messages.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $text_prefix = 'COM_USERS_USERS';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
* @param CMSApplication $app The CMSApplication for the dispatcher
|
||||
* @param Input $input Input
|
||||
*
|
||||
* @since 1.6
|
||||
* @see BaseController
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null, $app = null, $input = null)
|
||||
{
|
||||
parent::__construct($config, $factory, $app, $input);
|
||||
|
||||
$this->registerTask('block', 'changeBlock');
|
||||
$this->registerTask('unblock', 'changeBlock');
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for getModel.
|
||||
*
|
||||
* @param string $name The model name. Optional.
|
||||
* @param string $prefix The class prefix. Optional.
|
||||
* @param array $config Configuration array for model. Optional.
|
||||
*
|
||||
* @return object The model.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getModel($name = 'User', $prefix = 'Administrator', $config = ['ignore_request' => true])
|
||||
{
|
||||
return parent::getModel($name, $prefix, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to change the block status on a record.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function changeBlock()
|
||||
{
|
||||
// Check for request forgeries.
|
||||
$this->checkToken();
|
||||
|
||||
$ids = (array) $this->input->get('cid', [], 'int');
|
||||
$values = ['block' => 1, 'unblock' => 0];
|
||||
$task = $this->getTask();
|
||||
$value = ArrayHelper::getValue($values, $task, 0, 'int');
|
||||
|
||||
// Remove zero values resulting from input filter
|
||||
$ids = array_filter($ids);
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->setMessage(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'), 'warning');
|
||||
} else {
|
||||
// Get the model.
|
||||
$model = $this->getModel();
|
||||
|
||||
// Change the state of the records.
|
||||
if (!$model->block($ids, $value)) {
|
||||
$this->setMessage($model->getError(), 'error');
|
||||
} else {
|
||||
if ($value == 1) {
|
||||
$this->setMessage(Text::plural('COM_USERS_N_USERS_BLOCKED', \count($ids)));
|
||||
} elseif ($value == 0) {
|
||||
$this->setMessage(Text::plural('COM_USERS_N_USERS_UNBLOCKED', \count($ids)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setRedirect('index.php?option=com_users&view=users');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to activate a record.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
// Check for request forgeries.
|
||||
$this->checkToken();
|
||||
|
||||
$ids = (array) $this->input->get('cid', [], 'int');
|
||||
|
||||
// Remove zero values resulting from input filter
|
||||
$ids = array_filter($ids);
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->setMessage(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'), 'error');
|
||||
} else {
|
||||
// Get the model.
|
||||
$model = $this->getModel();
|
||||
|
||||
// Change the state of the records.
|
||||
if (!$model->activate($ids)) {
|
||||
$this->setMessage($model->getError(), 'error');
|
||||
} else {
|
||||
$this->setMessage(Text::plural('COM_USERS_N_USERS_ACTIVATED', \count($ids)));
|
||||
}
|
||||
}
|
||||
|
||||
$this->setRedirect('index.php?option=com_users&view=users');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the number of active users
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getQuickiconContent()
|
||||
{
|
||||
$model = $this->getModel('Users');
|
||||
|
||||
$model->setState('filter.state', 0);
|
||||
|
||||
$amount = (int) $model->getTotal();
|
||||
|
||||
$result = [];
|
||||
|
||||
$result['amount'] = $amount;
|
||||
$result['sronly'] = Text::plural('COM_USERS_N_QUICKICON_SRONLY', $amount);
|
||||
$result['name'] = Text::plural('COM_USERS_N_QUICKICON', $amount);
|
||||
|
||||
echo new JsonResponse($result);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\DataShape;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* @property string $pre_message Custom HTML to display above the MFA form
|
||||
* @property string $field_type How to render the MFA code field. "input" or "custom".
|
||||
* @property string $input_type The type attribute for the HTML input box. Typically "text" or "password".
|
||||
* @property string $placeholder Placeholder text for the HTML input box. Leave empty if you don't need it.
|
||||
* @property string $label Label to show above the HTML input box. Leave empty if you don't need it.
|
||||
* @property string $html Custom HTML. Only used when field_type = custom.
|
||||
* @property string $post_message Custom HTML to display below the MFA form
|
||||
* @property bool $hide_submit Should I hide the default Submit button?
|
||||
* @property bool $allowEntryBatching Is this method validating against all configured authenticators of this type?
|
||||
* @property string $help_url URL for help content
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class CaptiveRenderOptions extends DataShapeObject
|
||||
{
|
||||
/**
|
||||
* Display a standard HTML5 input field. Use the input_type, placeholder and label properties to set it up.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public const FIELD_INPUT = 'input';
|
||||
|
||||
/**
|
||||
* Display a custom HTML document. Use the html property to set it up.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public const FIELD_CUSTOM = 'custom';
|
||||
|
||||
/**
|
||||
* Custom HTML to display above the MFA form
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $pre_message = '';
|
||||
|
||||
/**
|
||||
* How to render the MFA code field. "input" (HTML input element) or "custom" (custom HTML)
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $field_type = 'input';
|
||||
|
||||
/**
|
||||
* The type attribute for the HTML input box. Typically "text" or "password". Use any HTML5 input type.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $input_type = '';
|
||||
|
||||
/**
|
||||
* Attributes other than type and id which will be added to the HTML input box.
|
||||
*
|
||||
* @var array
|
||||
* @@since 4.2.0
|
||||
*/
|
||||
protected $input_attributes = [];
|
||||
|
||||
/**
|
||||
* Placeholder text for the HTML input box. Leave empty if you don't need it.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $placeholder = '';
|
||||
|
||||
/**
|
||||
* Label to show above the HTML input box. Leave empty if you don't need it.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* Custom HTML. Only used when field_type = custom.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $html = '';
|
||||
|
||||
/**
|
||||
* Custom HTML to display below the MFA form
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $post_message = '';
|
||||
|
||||
/**
|
||||
* Should I hide the default Submit button?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $hide_submit = false;
|
||||
|
||||
/**
|
||||
* Additional CSS classes for the submit button (apply the MFA setup)
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $submit_class = '';
|
||||
|
||||
/**
|
||||
* Icon class to use for the submit button
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $submit_icon = 'icon icon-rightarrow icon-arrow-right';
|
||||
|
||||
/**
|
||||
* Language key to use for the text on the submit button
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $submit_text = 'COM_USERS_MFA_VALIDATE';
|
||||
|
||||
/**
|
||||
* Is this MFA method validating against all configured authenticators of the same type?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $allowEntryBatching = true;
|
||||
|
||||
/**
|
||||
* URL for help content
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $help_url = '';
|
||||
|
||||
/**
|
||||
* Setter for the field_type property
|
||||
*
|
||||
* @param string $value One of self::FIELD_INPUT, self::FIELD_CUSTOM
|
||||
*
|
||||
* @since 4.2.0
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
// phpcs:ignore
|
||||
protected function setField_type(string $value)
|
||||
{
|
||||
if (!\in_array($value, [self::FIELD_INPUT, self::FIELD_CUSTOM])) {
|
||||
throw new \InvalidArgumentException('Invalid value for property field_type.');
|
||||
}
|
||||
|
||||
$this->field_type = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the input_attributes property.
|
||||
*
|
||||
* @param array $value The value to set
|
||||
*
|
||||
* @return void
|
||||
* @@since 4.2.0
|
||||
*/
|
||||
// phpcs:ignore
|
||||
protected function setInput_attributes(array $value)
|
||||
{
|
||||
$forbiddenAttributes = ['id', 'type', 'name', 'value'];
|
||||
|
||||
foreach ($forbiddenAttributes as $key) {
|
||||
if (isset($value[$key])) {
|
||||
unset($value[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->input_attributes = $value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\DataShape;
|
||||
|
||||
/**
|
||||
* Generic helper for handling data shapes in com_users
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
abstract class DataShapeObject implements \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param array $array The data to initialise this object with
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(array $array = [])
|
||||
{
|
||||
if (!\is_array($array) && !($array instanceof self)) {
|
||||
throw new \InvalidArgumentException(sprintf('%s needs an array or a %s object', __METHOD__, __CLASS__));
|
||||
}
|
||||
|
||||
foreach (($array instanceof self) ? $array->asArray() : $array as $k => $v) {
|
||||
$this[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data shape as a key-value array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function asArray(): array
|
||||
{
|
||||
return get_object_vars($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge another data shape object or key-value array into this object.
|
||||
*
|
||||
* @param array|self $newValues The object or array to merge into self.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function merge($newValues): self
|
||||
{
|
||||
if (!\is_array($newValues) && !($newValues instanceof self)) {
|
||||
throw new \InvalidArgumentException(sprintf('%s needs an array or a %s object', __METHOD__, __CLASS__));
|
||||
}
|
||||
|
||||
foreach (($newValues instanceof self) ? $newValues->asArray() : $newValues as $k => $v) {
|
||||
if (!isset($this->{$k})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this[$k] = $v;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter
|
||||
*
|
||||
* @param string $name The name of the property to retrieve
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$methodName = 'get' . ucfirst($name);
|
||||
|
||||
if (method_exists($this, $methodName)) {
|
||||
return $this->{$methodName};
|
||||
}
|
||||
|
||||
if (property_exists($this, $name)) {
|
||||
return $this->{$name};
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Property %s not found in %s', $name, __CLASS__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Setter
|
||||
*
|
||||
* @param string $name The property to set the value for
|
||||
* @param mixed $value The property value to set it to
|
||||
*
|
||||
* @return mixed
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$methodName = 'set' . ucfirst($name);
|
||||
|
||||
if (method_exists($this, $methodName)) {
|
||||
return $this->{$methodName}($value);
|
||||
}
|
||||
|
||||
if (property_exists($this, $name)) {
|
||||
$this->{$name} = $value;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Property %s not found in %s', $name, __CLASS__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a property set?
|
||||
*
|
||||
* @param string $name Property name
|
||||
*
|
||||
* @return boolean Does it exist in the object?
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __isset($name): bool
|
||||
{
|
||||
$methodName = 'get' . ucfirst($name);
|
||||
|
||||
return method_exists($this, $methodName) || property_exists($this, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the property exist (array access)?
|
||||
*
|
||||
* @param string $offset Property name
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->{$offset});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a property (array access).
|
||||
*
|
||||
* @param string $offset Property name
|
||||
*
|
||||
* @return mixed
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function offsetGet($offset): mixed
|
||||
{
|
||||
return $this->{$offset};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a property (array access).
|
||||
*
|
||||
* @param string $offset Property name
|
||||
* @param mixed $value Property value
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
$this->{$offset} = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a property (array access).
|
||||
*
|
||||
* @param string $offset Property name
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new \LogicException(sprintf('You cannot unset members of %s', __CLASS__));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\DataShape;
|
||||
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* @property string $name Internal code of this MFA Method
|
||||
* @property string $display User-facing name for this MFA Method
|
||||
* @property string $shortinfo Short description of this MFA Method displayed to the user
|
||||
* @property string $image URL to the logo image for this Method
|
||||
* @property bool $canDisable Are we allowed to disable it?
|
||||
* @property bool $allowMultiple Are we allowed to have multiple instances of it per user?
|
||||
* @property string $help_url URL for help content
|
||||
* @property bool $allowEntryBatching Allow authentication against all entries of this MFA Method.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class MethodDescriptor extends DataShapeObject
|
||||
{
|
||||
/**
|
||||
* Internal code of this MFA Method
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* User-facing name for this MFA Method
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $display = '';
|
||||
|
||||
/**
|
||||
* Short description of this MFA Method displayed to the user
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $shortinfo = '';
|
||||
|
||||
/**
|
||||
* URL to the logo image for this Method
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $image = '';
|
||||
|
||||
/**
|
||||
* Are we allowed to disable it?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $canDisable = true;
|
||||
|
||||
/**
|
||||
* Are we allowed to have multiple instances of it per user?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $allowMultiple = false;
|
||||
|
||||
/**
|
||||
* URL for help content
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $help_url = '';
|
||||
|
||||
/**
|
||||
* Allow authentication against all entries of this MFA Method.
|
||||
*
|
||||
* Otherwise authentication takes place against a SPECIFIC entry at a time.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $allowEntryBatching = false;
|
||||
|
||||
/**
|
||||
* Active authentication methods, used internally only
|
||||
*
|
||||
* @var MfaTable[]
|
||||
* @since 4.2.0
|
||||
* @internal
|
||||
*/
|
||||
protected $active = [];
|
||||
|
||||
/**
|
||||
* Adds an active MFA method
|
||||
*
|
||||
* @param MfaTable $record The MFA method record to add
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function addActiveMethod(MfaTable $record)
|
||||
{
|
||||
$this->active[$record->id] = $record;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\DataShape;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Data shape for Method Setup Render Options
|
||||
*
|
||||
* @property string $default_title Default title if you are setting up this MFA Method for the first time
|
||||
* @property string $pre_message Custom HTML to display above the MFA setup form
|
||||
* @property string $table_heading Heading for displayed tabular data. Typically used to display a list of fixed MFA
|
||||
* codes, TOTP setup parameters etc
|
||||
* @property array $tabular_data Any tabular data to display (label => custom HTML). See above
|
||||
* @property array $hidden_data Hidden fields to include in the form (name => value)
|
||||
* @property string $field_type How to render the MFA setup code field. "input" (HTML input element) or "custom"
|
||||
* (custom HTML)
|
||||
* @property string $input_type The type attribute for the HTML input box. Typically "text" or "password". Use any
|
||||
* HTML5 input type.
|
||||
* @property string $input_value Pre-filled value for the HTML input box. Typically used for fixed codes, the fixed
|
||||
* YubiKey ID etc.
|
||||
* @property string $placeholder Placeholder text for the HTML input box. Leave empty if you don't need it.
|
||||
* @property string $label Label to show above the HTML input box. Leave empty if you don't need it.
|
||||
* @property string $html Custom HTML. Only used when field_type = custom.
|
||||
* @property bool $show_submit Should I show the submit button (apply the MFA setup)?
|
||||
* @property string $submit_class Additional CSS classes for the submit button (apply the MFA setup)
|
||||
* @property string $post_message Custom HTML to display below the MFA setup form
|
||||
* @property string $help_url A URL with help content for this Method to display to the user
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class SetupRenderOptions extends DataShapeObject
|
||||
{
|
||||
/**
|
||||
* Display a standard HTML5 input field. Use the input_type, placeholder and label properties to set it up.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public const FIELD_INPUT = 'input';
|
||||
|
||||
/**
|
||||
* Display a custom HTML document. Use the html property to set it up.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public const FIELD_CUSTOM = 'custom';
|
||||
|
||||
/**
|
||||
* Default title if you are setting up this MFA Method for the first time
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $default_title = '';
|
||||
|
||||
/**
|
||||
* Custom HTML to display above the MFA setup form parameters etc
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $pre_message = '';
|
||||
|
||||
/**
|
||||
* Heading for displayed tabular data. Typically used to display a list of fixed MFA codes, TOTP setup
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $table_heading = '';
|
||||
|
||||
/**
|
||||
* Any tabular data to display (label => custom HTML). See above
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $tabular_data = [];
|
||||
|
||||
/**
|
||||
* Hidden fields to include in the form (name => value)
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $hidden_data = [];
|
||||
|
||||
/**
|
||||
* How to render the MFA setup code field. "input" (HTML input element) or "custom" (custom HTML)
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $field_type = 'input';
|
||||
|
||||
/**
|
||||
* The type attribute for the HTML input box. Typically "text" or "password". Use any HTML5 input type.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $input_type = 'text';
|
||||
|
||||
/**
|
||||
* Attributes other than type and id which will be added to the HTML input box.
|
||||
*
|
||||
* @var array
|
||||
* @@since 4.2.0
|
||||
*/
|
||||
protected $input_attributes = [];
|
||||
|
||||
/**
|
||||
* Pre-filled value for the HTML input box. Typically used for fixed codes, the fixed YubiKey ID etc.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $input_value = '';
|
||||
|
||||
/**
|
||||
* Placeholder text for the HTML input box. Leave empty if you don't need it.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $placeholder = '';
|
||||
|
||||
/**
|
||||
* Label to show above the HTML input box. Leave empty if you don't need it.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* Custom HTML. Only used when field_type = custom.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $html = '';
|
||||
|
||||
/**
|
||||
* Should I show the submit button (apply the MFA setup)?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $show_submit = true;
|
||||
|
||||
/**
|
||||
* Additional CSS classes for the submit button (apply the MFA setup)
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $submit_class = '';
|
||||
|
||||
/**
|
||||
* Icon class to use for the submit button
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $submit_icon = 'icon icon-ok';
|
||||
|
||||
/**
|
||||
* Language key to use for the text on the submit button
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $submit_text = 'JSAVE';
|
||||
|
||||
/**
|
||||
* Custom HTML to display below the MFA setup form
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $post_message = '';
|
||||
|
||||
/**
|
||||
* A URL with help content for this Method to display to the user
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $help_url = '';
|
||||
|
||||
/**
|
||||
* Setter for the field_type property
|
||||
*
|
||||
* @param string $value One of self::FIELD_INPUT, self::FIELD_CUSTOM
|
||||
*
|
||||
* @since 4.2.0
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
// phpcs:ignore
|
||||
protected function setField_type($value)
|
||||
{
|
||||
if (!\in_array($value, [self::FIELD_INPUT, self::FIELD_CUSTOM])) {
|
||||
throw new \InvalidArgumentException('Invalid value for property field_type.');
|
||||
}
|
||||
|
||||
$this->field_type = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the input_attributes property.
|
||||
*
|
||||
* @param array $value The value to set
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
// phpcs:ignore
|
||||
protected function setInput_attributes(array $value)
|
||||
{
|
||||
$forbiddenAttributes = ['id', 'type', 'name', 'value'];
|
||||
|
||||
foreach ($forbiddenAttributes as $key) {
|
||||
if (isset($value[$key])) {
|
||||
unset($value[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->input_attributes = $value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Dispatcher;
|
||||
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcher;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* ComponentDispatcher class for com_users
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Dispatcher extends ComponentDispatcher
|
||||
{
|
||||
/**
|
||||
* Override checkAccess to allow users edit profile without having to have core.manager permission
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function checkAccess()
|
||||
{
|
||||
$task = $this->input->getCmd('task');
|
||||
$view = $this->input->getCmd('view');
|
||||
$layout = $this->input->getCmd('layout');
|
||||
$allowedTasks = ['user.edit', 'user.apply', 'user.save', 'user.cancel'];
|
||||
|
||||
// Allow users to edit their own account
|
||||
if (\in_array($task, $allowedTasks, true) || ($view === 'user' && $layout === 'edit')) {
|
||||
$user = $this->app->getIdentity();
|
||||
$id = $this->input->getInt('id');
|
||||
|
||||
if ((int) $user->id === $id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case: Multi-factor Authentication
|
||||
*
|
||||
* We allow access to all MFA views and tasks. Access control for MFA tasks is performed in
|
||||
* the Controllers since what is allowed depends on who is logged in and whose account you
|
||||
* are trying to modify. Implementing these checks in the Dispatcher would violate the
|
||||
* separation of concerns.
|
||||
*/
|
||||
$allowedViews = ['callback', 'captive', 'method', 'methods'];
|
||||
$isAllowedTask = array_reduce(
|
||||
$allowedViews,
|
||||
function ($carry, $taskPrefix) use ($task) {
|
||||
return $carry || strpos($task ?? '', $taskPrefix . '.') === 0;
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
if (\in_array(strtolower($view ?? ''), $allowedViews) || $isAllowedTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::checkAccess();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Extension;
|
||||
|
||||
use Joomla\CMS\Component\Router\RouterServiceInterface;
|
||||
use Joomla\CMS\Component\Router\RouterServiceTrait;
|
||||
use Joomla\CMS\Extension\BootableExtensionInterface;
|
||||
use Joomla\CMS\Extension\MVCComponent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Fields\FieldsServiceInterface;
|
||||
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Service\HTML\Users;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Component class for com_users
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class UsersComponent extends MVCComponent implements BootableExtensionInterface, RouterServiceInterface, FieldsServiceInterface
|
||||
{
|
||||
use RouterServiceTrait;
|
||||
use HTMLRegistryAwareTrait;
|
||||
|
||||
/**
|
||||
* Booting the extension. This is the function to set up the environment of the extension like
|
||||
* registering new class loaders, etc.
|
||||
*
|
||||
* If required, some initial set up can be done from services of the container, eg.
|
||||
* registering HTML services.
|
||||
*
|
||||
* @param ContainerInterface $container The container
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function boot(ContainerInterface $container)
|
||||
{
|
||||
$this->getRegistry()->register('users', new Users());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid section for the given section. If it is not valid then null is returned.
|
||||
*
|
||||
* @param string $section The section to get the mapping for
|
||||
* @param object|null $item The content item or null
|
||||
*
|
||||
* @return string|null The new section or null
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function validateSection($section, $item = null)
|
||||
{
|
||||
if (Factory::getApplication()->isClient('site')) {
|
||||
switch ($section) {
|
||||
case 'registration':
|
||||
case 'profile':
|
||||
return 'user';
|
||||
}
|
||||
}
|
||||
|
||||
if ($section === 'user') {
|
||||
return $section;
|
||||
}
|
||||
|
||||
// We don't know other sections.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns valid contexts.
|
||||
*
|
||||
* @return array Associative array with contexts as keys and translated strings as values
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getContexts(): array
|
||||
{
|
||||
$language = Factory::getApplication()->getLanguage();
|
||||
$language->load('com_users', JPATH_ADMINISTRATOR);
|
||||
|
||||
return [
|
||||
'com_users.user' => $language->_('COM_USERS'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Field;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Form\Field\ListField;
|
||||
use Joomla\CMS\Helper\UserGroupsHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User Group Parent field..
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class GroupparentField extends ListField
|
||||
{
|
||||
/**
|
||||
* The form field type.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $type = 'GroupParent';
|
||||
|
||||
/**
|
||||
* Method to clean the Usergroup Options from all children starting by a given father
|
||||
*
|
||||
* @param array $userGroupsOptions The usergroup options to clean
|
||||
* @param integer $fatherId The father ID to start with
|
||||
*
|
||||
* @return array The cleaned field options
|
||||
*
|
||||
* @since 3.9.4
|
||||
*/
|
||||
private function cleanOptionsChildrenByFather($userGroupsOptions, $fatherId)
|
||||
{
|
||||
foreach ($userGroupsOptions as $userGroupsOptionsId => $userGroupsOptionsData) {
|
||||
if ((int) $userGroupsOptionsData->parent_id === (int) $fatherId) {
|
||||
unset($userGroupsOptions[$userGroupsOptionsId]);
|
||||
|
||||
$userGroupsOptions = $this->cleanOptionsChildrenByFather($userGroupsOptions, $userGroupsOptionsId);
|
||||
}
|
||||
}
|
||||
|
||||
return $userGroupsOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the field options.
|
||||
*
|
||||
* @return array The field option objects
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
$options = UserGroupsHelper::getInstance()->getAll();
|
||||
$currentGroupId = (int) Factory::getApplication()->getInput()->get('id', 0, 'int');
|
||||
|
||||
// Prevent to set yourself as parent
|
||||
if ($currentGroupId) {
|
||||
unset($options[$currentGroupId]);
|
||||
}
|
||||
|
||||
// We should not remove any groups when we are creating a new group
|
||||
if ($currentGroupId !== 0) {
|
||||
// Prevent parenting direct children and children of children of this item.
|
||||
$options = $this->cleanOptionsChildrenByFather($options, $currentGroupId);
|
||||
}
|
||||
|
||||
$options = array_values($options);
|
||||
$isSuperAdmin = $this->getCurrentUser()->authorise('core.admin');
|
||||
|
||||
// Pad the option text with spaces using depth level as a multiplier.
|
||||
for ($i = 0, $n = \count($options); $i < $n; $i++) {
|
||||
// Show groups only if user is super admin or group is not super admin
|
||||
if ($isSuperAdmin || !Access::checkGroup($options[$i]->id, 'core.admin')) {
|
||||
$options[$i]->value = $options[$i]->id;
|
||||
$options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->title;
|
||||
} else {
|
||||
unset($options[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge any additional options in the XML definition.
|
||||
return array_merge(parent::getOptions(), $options);
|
||||
}
|
||||
}
|
||||
47
administrator/components/com_users/src/Field/LevelsField.php
Normal file
47
administrator/components/com_users/src/Field/LevelsField.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Field;
|
||||
|
||||
use Joomla\CMS\Form\Field\ListField;
|
||||
use Joomla\Component\Users\Administrator\Helper\DebugHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Access Levels field.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
class LevelsField extends ListField
|
||||
{
|
||||
/**
|
||||
* The form field type.
|
||||
*
|
||||
* @var string
|
||||
* @since 3.6.0
|
||||
*/
|
||||
protected $type = 'Levels';
|
||||
|
||||
/**
|
||||
* Method to get the field options.
|
||||
*
|
||||
* @return array The field option objects
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
// Merge any additional options in the XML definition.
|
||||
return array_merge(parent::getOptions(), DebugHelper::getLevelsOptions());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Field;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Select modules positions.
|
||||
*
|
||||
* Reuses the same field from com_modules. Don't lose it; reuse it!
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class ModulesPositionField extends \Joomla\Component\Modules\Administrator\Field\ModulesPositionField
|
||||
{
|
||||
}
|
||||
163
administrator/components/com_users/src/Helper/DebugHelper.php
Normal file
163
administrator/components/com_users/src/Helper/DebugHelper.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Helper;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users component debugging helper.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class DebugHelper
|
||||
{
|
||||
/**
|
||||
* Get a list of the components.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function getComponents()
|
||||
{
|
||||
// Initialise variable.
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('name AS text, element AS value')
|
||||
->from('#__extensions')
|
||||
->where('enabled >= 1')
|
||||
->where('type =' . $db->quote('component'));
|
||||
|
||||
$items = $db->setQuery($query)->loadObjectList();
|
||||
|
||||
if (\count($items)) {
|
||||
$lang = Factory::getLanguage();
|
||||
|
||||
foreach ($items as &$item) {
|
||||
// Load language
|
||||
$extension = $item->value;
|
||||
$source = JPATH_ADMINISTRATOR . '/components/' . $extension;
|
||||
$lang->load("$extension.sys", JPATH_ADMINISTRATOR)
|
||||
|| $lang->load("$extension.sys", $source);
|
||||
|
||||
// Translate component name
|
||||
$item->text = Text::_($item->text);
|
||||
}
|
||||
|
||||
// Sort by component name
|
||||
$items = ArrayHelper::sortObjects($items, 'text', 1, true, true);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the actions for the component or code actions.
|
||||
*
|
||||
* @param string $component The name of the component.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function getDebugActions($component = null)
|
||||
{
|
||||
$actions = [];
|
||||
|
||||
// Try to get actions for the component
|
||||
if (!empty($component)) {
|
||||
$component_actions = Access::getActionsFromFile(JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml');
|
||||
|
||||
if (!empty($component_actions)) {
|
||||
foreach ($component_actions as &$action) {
|
||||
$descr = (string) $action->title;
|
||||
|
||||
if (!empty($action->description)) {
|
||||
$descr = (string) $action->description;
|
||||
}
|
||||
|
||||
$actions[$action->title] = [$action->name, $descr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use default actions from configuration if no component selected or component doesn't have actions
|
||||
if (empty($actions)) {
|
||||
$filename = JPATH_ADMINISTRATOR . '/components/com_config/forms/application.xml';
|
||||
|
||||
if (is_file($filename)) {
|
||||
$xml = simplexml_load_file($filename);
|
||||
|
||||
foreach ($xml->children()->fieldset as $fieldset) {
|
||||
if ('permissions' == (string) $fieldset['name']) {
|
||||
foreach ($fieldset->children() as $field) {
|
||||
if ('rules' == (string) $field['name']) {
|
||||
foreach ($field->children() as $action) {
|
||||
$descr = (string) $action['title'];
|
||||
|
||||
if (isset($action['description']) && !empty($action['description'])) {
|
||||
$descr = (string) $action['description'];
|
||||
}
|
||||
|
||||
$actions[(string) $action['title']] = [
|
||||
(string) $action['name'],
|
||||
$descr,
|
||||
];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load language
|
||||
$lang = Factory::getLanguage();
|
||||
$extension = 'com_config';
|
||||
$source = JPATH_ADMINISTRATOR . '/components/' . $extension;
|
||||
|
||||
$lang->load($extension, JPATH_ADMINISTRATOR, null, false, false)
|
||||
|| $lang->load($extension, $source, null, false, false)
|
||||
|| $lang->load($extension, JPATH_ADMINISTRATOR, $lang->getDefault(), false, false)
|
||||
|| $lang->load($extension, $source, $lang->getDefault(), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of filter options for the levels.
|
||||
*
|
||||
* @return array An array of \JHtmlOption elements.
|
||||
*/
|
||||
public static function getLevelsOptions()
|
||||
{
|
||||
// Build the filter options.
|
||||
$options = [];
|
||||
$options[] = HTMLHelper::_('select.option', '1', Text::sprintf('COM_USERS_OPTION_LEVEL_COMPONENT', 1));
|
||||
$options[] = HTMLHelper::_('select.option', '2', Text::sprintf('COM_USERS_OPTION_LEVEL_CATEGORY', 2));
|
||||
$options[] = HTMLHelper::_('select.option', '3', Text::sprintf('COM_USERS_OPTION_LEVEL_DEEPER', 3));
|
||||
$options[] = HTMLHelper::_('select.option', '4', '4');
|
||||
$options[] = HTMLHelper::_('select.option', '5', '5');
|
||||
$options[] = HTMLHelper::_('select.option', '6', '6');
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
362
administrator/components/com_users/src/Helper/Mfa.php
Normal file
362
administrator/components/com_users/src/Helper/Mfa.php
Normal file
@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Helper;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Document\HtmlDocument;
|
||||
use Joomla\CMS\Event\MultiFactor\GetMethod;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Component\Users\Administrator\DataShape\MethodDescriptor;
|
||||
use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
|
||||
use Joomla\Component\Users\Administrator\Model\MethodsModel;
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
use Joomla\Component\Users\Administrator\View\Methods\HtmlView;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Helper functions for captive MFA handling
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
abstract class Mfa
|
||||
{
|
||||
/**
|
||||
* Cache of all currently active MFAs
|
||||
*
|
||||
* @var array|null
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected static $allMFAs = null;
|
||||
|
||||
/**
|
||||
* Are we inside the administrator application
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected static $isAdmin = null;
|
||||
|
||||
/**
|
||||
* Get the HTML for the Multi-factor Authentication configuration interface for a user.
|
||||
*
|
||||
* This helper method uses a sort of primitive HMVC to display the com_users' Methods page which
|
||||
* renders the MFA configuration interface.
|
||||
*
|
||||
* @param User $user The user we are going to show the configuration UI for.
|
||||
*
|
||||
* @return string|null The HTML of the UI; null if we cannot / must not show it.
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function getConfigurationInterface(User $user): ?string
|
||||
{
|
||||
// Check the conditions
|
||||
if (!self::canShowConfigurationInterface($user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var CMSApplication $app */
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if (!$app->getInput()->getCmd('option', '') === 'com_users') {
|
||||
$app->getLanguage()->load('com_users');
|
||||
$app->getDocument()
|
||||
->getWebAssetManager()
|
||||
->getRegistry()
|
||||
->addExtensionRegistryFile('com_users');
|
||||
}
|
||||
|
||||
// Get a model
|
||||
/** @var MVCFactoryInterface $factory */
|
||||
$factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
|
||||
|
||||
/** @var MethodsModel $methodsModel */
|
||||
$methodsModel = $factory->createModel('Methods', 'Administrator');
|
||||
/** @var BackupcodesModel $methodsModel */
|
||||
$backupCodesModel = $factory->createModel('Backupcodes', 'Administrator');
|
||||
|
||||
// Get a view object
|
||||
$appRoot = $app->isClient('site') ? \JPATH_SITE : \JPATH_ADMINISTRATOR;
|
||||
$prefix = $app->isClient('site') ? 'Site' : 'Administrator';
|
||||
/** @var HtmlView $view */
|
||||
$view = $factory->createView(
|
||||
'Methods',
|
||||
$prefix,
|
||||
'Html',
|
||||
[
|
||||
'base_path' => $appRoot . '/components/com_users',
|
||||
]
|
||||
);
|
||||
$view->setModel($methodsModel, true);
|
||||
/** @noinspection PhpParamsInspection */
|
||||
$view->setModel($backupCodesModel);
|
||||
$view->document = $app->getDocument();
|
||||
$view->returnURL = base64_encode(Uri::getInstance()->toString());
|
||||
$view->user = $user;
|
||||
$view->set('forHMVC', true);
|
||||
$view->setLanguage($app->getLanguage());
|
||||
|
||||
@ob_start();
|
||||
|
||||
try {
|
||||
$view->display();
|
||||
} catch (\Throwable $e) {
|
||||
@ob_end_clean();
|
||||
|
||||
/**
|
||||
* This is intentional! When you are developing a Multi-factor Authentication plugin you
|
||||
* will inevitably mess something up and end up with an error. This would cause the
|
||||
* entire MFA configuration page to disappear. No problem! Set Debug System to Yes in
|
||||
* Global Configuration and you can see the error exception which will help you solve
|
||||
* your problem.
|
||||
*/
|
||||
if (\defined('JDEBUG') && JDEBUG) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return @ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all of the MFA Methods
|
||||
*
|
||||
* @return MethodDescriptor[]
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function getMfaMethods(): array
|
||||
{
|
||||
PluginHelper::importPlugin('multifactorauth');
|
||||
|
||||
if (\is_null(self::$allMFAs)) {
|
||||
// Get all the plugin results
|
||||
$event = new GetMethod();
|
||||
$temp = Factory::getApplication()
|
||||
->getDispatcher()
|
||||
->dispatch($event->getName(), $event)
|
||||
->getArgument('result', []);
|
||||
|
||||
// Normalize the results
|
||||
self::$allMFAs = [];
|
||||
|
||||
foreach ($temp as $method) {
|
||||
if (!\is_array($method) && !($method instanceof MethodDescriptor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$method = $method instanceof MethodDescriptor
|
||||
? $method : new MethodDescriptor($method);
|
||||
|
||||
if (empty($method['name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$allMFAs[$method['name']] = $method;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$allMFAs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current user allowed to add/edit MFA methods for $user?
|
||||
*
|
||||
* This is only allowed if I am adding / editing methods for myself.
|
||||
*
|
||||
* If the target user is a member of any group disallowed to use MFA this will return false.
|
||||
*
|
||||
* @param User|null $user The user you want to know if we're allowed to edit
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function canAddEditMethod(?User $user = null): bool
|
||||
{
|
||||
// Cannot do MFA operations on no user or a guest user.
|
||||
if (\is_null($user) || $user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the user is in a user group which disallows MFA we cannot allow adding / editing methods.
|
||||
$neverMFAGroups = ComponentHelper::getParams('com_users')->get('neverMFAUserGroups', []);
|
||||
$neverMFAGroups = \is_array($neverMFAGroups) ? $neverMFAGroups : [];
|
||||
|
||||
if (\count(array_intersect($user->getAuthorisedGroups(), $neverMFAGroups))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this is the same as the logged-in user.
|
||||
$myUser = Factory::getApplication()->getIdentity()
|
||||
?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
|
||||
|
||||
return $myUser->id === $user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current user allowed to delete MFA methods / disable MFA for $user?
|
||||
*
|
||||
* This is allowed if:
|
||||
* - The user being queried is the same as the logged-in user
|
||||
* - The logged-in user is a Super User AND the queried user is NOT a Super User.
|
||||
*
|
||||
* Note that Super Users can be edited by their own user only for security reasons. If a Super
|
||||
* User gets locked out they must use the Backup Codes to regain access. If that's not possible,
|
||||
* they will need to delete their records from the `#__user_mfa` table.
|
||||
*
|
||||
* @param User|null $user The user being queried.
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function canDeleteMethod(?User $user = null): bool
|
||||
{
|
||||
// Cannot do MFA operations on no user or a guest user.
|
||||
if (\is_null($user) || $user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$myUser = Factory::getApplication()->getIdentity()
|
||||
?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
|
||||
|
||||
return $myUser->id === $user->id
|
||||
|| ($myUser->authorise('core.admin') && !$user->authorise('core.admin'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all MFA records for a specific user
|
||||
*
|
||||
* @param int|null $userId User ID. NULL for currently logged in user.
|
||||
*
|
||||
* @return MfaTable[]
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function getUserMfaRecords(?int $userId): array
|
||||
{
|
||||
if (empty($userId)) {
|
||||
$user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
|
||||
$userId = $user->id ?: 0;
|
||||
}
|
||||
|
||||
/** @var DatabaseInterface $db */
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName('#__user_mfa'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER);
|
||||
|
||||
try {
|
||||
$ids = $db->setQuery($query)->loadColumn() ?: [];
|
||||
} catch (\Exception $e) {
|
||||
$ids = [];
|
||||
}
|
||||
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var MVCFactoryInterface $factory */
|
||||
$factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
|
||||
|
||||
// Map all results to MFA table objects
|
||||
$records = array_map(
|
||||
function ($id) use ($factory) {
|
||||
/** @var MfaTable $record */
|
||||
$record = $factory->createTable('Mfa', 'Administrator');
|
||||
$loaded = $record->load($id);
|
||||
|
||||
return $loaded ? $record : null;
|
||||
},
|
||||
$ids
|
||||
);
|
||||
|
||||
// Let's remove Methods we couldn't decrypt when reading from the database.
|
||||
$hasBackupCodes = false;
|
||||
|
||||
$records = array_filter(
|
||||
$records,
|
||||
function ($record) use (&$hasBackupCodes) {
|
||||
$isValid = !\is_null($record) && (!empty($record->options));
|
||||
|
||||
if ($isValid && ($record->method === 'backupcodes')) {
|
||||
$hasBackupCodes = true;
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
);
|
||||
|
||||
// If the only Method is backup codes it's as good as having no records
|
||||
if ((\count($records) === 1) && $hasBackupCodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are the conditions for showing the MFA configuration interface met?
|
||||
*
|
||||
* @param User|null $user The user to be configured
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function canShowConfigurationInterface(?User $user = null): bool
|
||||
{
|
||||
// If I have no user to check against that's all the checking I can do.
|
||||
if (empty($user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// I need at least one MFA method plugin for the setup interface to make any sense.
|
||||
$plugins = PluginHelper::getPlugin('multifactorauth');
|
||||
|
||||
if (\count($plugins) < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var CMSApplication $app */
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// We can only show a configuration page in the front- or backend application.
|
||||
if (!$app->isClient('site') && !$app->isClient('administrator')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only show the configuration page if we have an HTML document
|
||||
if (!($app->getDocument() instanceof HtmlDocument)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// I must be able to add, edit or delete the user's MFA settings
|
||||
return self::canAddEditMethod($user) || self::canDeleteMethod($user);
|
||||
}
|
||||
}
|
||||
192
administrator/components/com_users/src/Helper/UsersHelper.php
Normal file
192
administrator/components/com_users/src/Helper/UsersHelper.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Helper;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Helper\UserGroupsHelper;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Object\CMSObject;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users component helper.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class UsersHelper extends ContentHelper
|
||||
{
|
||||
/**
|
||||
* @var CMSObject A cache for the available actions.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected static $actions;
|
||||
|
||||
/**
|
||||
* Get a list of filter options for the blocked state of a user.
|
||||
*
|
||||
* @return array An array of \JHtmlOption elements.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function getStateOptions()
|
||||
{
|
||||
// Build the filter options.
|
||||
$options = [];
|
||||
$options[] = HTMLHelper::_('select.option', '0', Text::_('JENABLED'));
|
||||
$options[] = HTMLHelper::_('select.option', '1', Text::_('JDISABLED'));
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of filter options for the activated state of a user.
|
||||
*
|
||||
* @return array An array of \JHtmlOption elements.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function getActiveOptions()
|
||||
{
|
||||
// Build the filter options.
|
||||
$options = [];
|
||||
$options[] = HTMLHelper::_('select.option', '0', Text::_('COM_USERS_ACTIVATED'));
|
||||
$options[] = HTMLHelper::_('select.option', '1', Text::_('COM_USERS_UNACTIVATED'));
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the user groups for filtering.
|
||||
*
|
||||
* @return array An array of \JHtmlOption elements.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static function getGroups()
|
||||
{
|
||||
$options = UserGroupsHelper::getInstance()->getAll();
|
||||
|
||||
foreach ($options as &$option) {
|
||||
$option->value = $option->id;
|
||||
$option->text = str_repeat('- ', $option->level) . $option->title;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of range options used in filter select list
|
||||
* used in com_users on users view
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public static function getRangeOptions()
|
||||
{
|
||||
$options = [
|
||||
HTMLHelper::_('select.option', 'today', Text::_('COM_USERS_OPTION_RANGE_TODAY')),
|
||||
HTMLHelper::_('select.option', 'past_week', Text::_('COM_USERS_OPTION_RANGE_PAST_WEEK')),
|
||||
HTMLHelper::_('select.option', 'past_1month', Text::_('COM_USERS_OPTION_RANGE_PAST_1MONTH')),
|
||||
HTMLHelper::_('select.option', 'past_3month', Text::_('COM_USERS_OPTION_RANGE_PAST_3MONTH')),
|
||||
HTMLHelper::_('select.option', 'past_6month', Text::_('COM_USERS_OPTION_RANGE_PAST_6MONTH')),
|
||||
HTMLHelper::_('select.option', 'past_year', Text::_('COM_USERS_OPTION_RANGE_PAST_YEAR')),
|
||||
HTMLHelper::_('select.option', 'post_year', Text::_('COM_USERS_OPTION_RANGE_POST_YEAR')),
|
||||
];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* No longer used.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @throws \Exception
|
||||
*
|
||||
* @deprecated 4.2 will be removed in 6.0
|
||||
* No longer used, will be removed without replacement
|
||||
*/
|
||||
public static function getTwoFactorMethods()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the User Groups for Viewing Access Levels
|
||||
*
|
||||
* @param string $rules User Groups in JSON format
|
||||
*
|
||||
* @return string $groups Comma separated list of User Groups
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public static function getVisibleByGroups($rules)
|
||||
{
|
||||
$rules = json_decode($rules);
|
||||
|
||||
if (!$rules) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('title', 'text'))
|
||||
->from($db->quoteName('#__usergroups'))
|
||||
->whereIn($db->quoteName('id'), $rules);
|
||||
$db->setQuery($query);
|
||||
|
||||
$groups = $db->loadColumn();
|
||||
$groups = implode(', ', $groups);
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid section for users. If it is not valid then null
|
||||
* is returned.
|
||||
*
|
||||
* @param string $section The section to get the mapping for
|
||||
*
|
||||
* @return string|null The new section
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @throws \Exception
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use \Joomla\Component\Users\Administrator\Extension\UsersComponent::validateSection() instead.
|
||||
*/
|
||||
public static function validateSection($section)
|
||||
{
|
||||
return Factory::getApplication()->bootComponent('com_users')->validateSection($section, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns valid contexts
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use \Joomla\Component\Users\Administrator\Extension\UsersComponent::getContexts() instead.
|
||||
*/
|
||||
public static function getContexts()
|
||||
{
|
||||
return Factory::getApplication()->bootComponent('com_users')->getContexts();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Crypt\Crypt;
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Model for managing backup codes
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class BackupcodesModel extends BaseDatabaseModel
|
||||
{
|
||||
/**
|
||||
* Caches the backup codes per user ID
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $cache = [];
|
||||
|
||||
/**
|
||||
* Get the backup codes record for the specified user
|
||||
*
|
||||
* @param User|null $user The user in question. Use null for the currently logged in user.
|
||||
*
|
||||
* @return MfaTable|null Record object or null if none is found
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getBackupCodesRecord(User $user = null): ?MfaTable
|
||||
{
|
||||
// Make sure I have a user
|
||||
if (empty($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
/** @var MfaTable $record */
|
||||
$record = $this->getTable('Mfa', 'Administrator');
|
||||
$loaded = $record->load(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'method' => 'backupcodes',
|
||||
]
|
||||
);
|
||||
|
||||
if (!$loaded) {
|
||||
$record = null;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new set of backup codes for the specified user. The generated codes are immediately saved to the
|
||||
* database and the internal cache is updated.
|
||||
*
|
||||
* @param User|null $user Which user to generate codes for?
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function regenerateBackupCodes(User $user = null): void
|
||||
{
|
||||
// Make sure I have a user
|
||||
if (empty($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
// Generate backup codes
|
||||
$backupCodes = [];
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
// Each backup code is 2 groups of 4 digits
|
||||
$backupCodes[$i] = sprintf('%04u%04u', random_int(0, 9999), random_int(0, 9999));
|
||||
}
|
||||
|
||||
// Save the backup codes to the database and update the cache
|
||||
$this->saveBackupCodes($backupCodes, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the backup codes to the database
|
||||
*
|
||||
* @param array $codes An array of exactly 10 elements
|
||||
* @param User|null $user The user for which to save the backup codes
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function saveBackupCodes(array $codes, ?User $user = null): bool
|
||||
{
|
||||
// Make sure I have a user
|
||||
if (empty($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
// Try to load existing backup codes
|
||||
$existingCodes = $this->getBackupCodes($user);
|
||||
$jNow = Date::getInstance();
|
||||
|
||||
/** @var MfaTable $record */
|
||||
$record = $this->getTable('Mfa', 'Administrator');
|
||||
|
||||
if (\is_null($existingCodes)) {
|
||||
$record->reset();
|
||||
|
||||
$newData = [
|
||||
'user_id' => $user->id,
|
||||
'title' => Text::_('COM_USERS_USER_BACKUPCODES'),
|
||||
'method' => 'backupcodes',
|
||||
'default' => 0,
|
||||
'created_on' => $jNow->toSql(),
|
||||
'options' => $codes,
|
||||
];
|
||||
} else {
|
||||
$record->load(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'method' => 'backupcodes',
|
||||
]
|
||||
);
|
||||
|
||||
$newData = [
|
||||
'options' => $codes,
|
||||
];
|
||||
}
|
||||
|
||||
$saved = $record->save($newData);
|
||||
|
||||
if (!$saved) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finally, update the cache
|
||||
$this->cache[$user->id] = $codes;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the backup codes for the specified user. Cached values will be preferentially returned, therefore you
|
||||
* MUST go through this model's Methods ONLY when dealing with backup codes.
|
||||
*
|
||||
* @param User|null $user The user for which you want the backup codes
|
||||
*
|
||||
* @return array|null The backup codes, or null if they do not exist
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getBackupCodes(User $user = null): ?array
|
||||
{
|
||||
// Make sure I have a user
|
||||
if (empty($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
if (isset($this->cache[$user->id])) {
|
||||
return $this->cache[$user->id];
|
||||
}
|
||||
|
||||
// If there is no cached record try to load it from the database
|
||||
$this->cache[$user->id] = null;
|
||||
|
||||
// Try to load the record
|
||||
/** @var MfaTable $record */
|
||||
$record = $this->getTable('Mfa', 'Administrator');
|
||||
$loaded = $record->load(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'method' => 'backupcodes',
|
||||
]
|
||||
);
|
||||
|
||||
if ($loaded) {
|
||||
$this->cache[$user->id] = $record->options;
|
||||
}
|
||||
|
||||
return $this->cache[$user->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided string is a backup code. If it is, it will be removed from the list (replaced with an empty
|
||||
* string) and the codes will be saved to the database. All comparisons are performed in a timing safe manner.
|
||||
*
|
||||
* @param string $code The code to check
|
||||
* @param User|null $user The user to check against
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function isBackupCode($code, ?User $user = null): bool
|
||||
{
|
||||
// Load the backup codes
|
||||
$codes = $this->getBackupCodes($user) ?: array_fill(0, 10, '');
|
||||
|
||||
// Keep only the numbers in the provided $code
|
||||
$code = filter_var($code, FILTER_SANITIZE_NUMBER_INT);
|
||||
$code = trim($code);
|
||||
|
||||
// Check if the code is in the array. We always check against ten codes to prevent timing attacks which
|
||||
// determine the amount of codes.
|
||||
$result = false;
|
||||
|
||||
// The two arrays let us always add an element to an array, therefore having PHP expend the same amount of time
|
||||
// for the correct code, the incorrect codes and the fake codes.
|
||||
$newArray = [];
|
||||
$dummyArray = [];
|
||||
|
||||
$realLength = \count($codes);
|
||||
$restLength = 10 - $realLength;
|
||||
|
||||
for ($i = 0; $i < $realLength; $i++) {
|
||||
if (hash_equals($codes[$i], $code)) {
|
||||
// This may seem redundant but makes sure both branches of the if-block are isochronous
|
||||
$result = $result || true;
|
||||
$newArray[] = '';
|
||||
$dummyArray[] = $codes[$i];
|
||||
} else {
|
||||
// This may seem redundant but makes sure both branches of the if-block are isochronous
|
||||
$result = $result || false;
|
||||
$dummyArray[] = '';
|
||||
$newArray[] = $codes[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an intentional waste of time, symmetrical to the code above, making sure
|
||||
* evaluating each of the total of ten elements takes the same time. This code should never
|
||||
* run UNLESS someone messed up with our backup codes array and it no longer contains 10
|
||||
* elements.
|
||||
*/
|
||||
$otherResult = false;
|
||||
|
||||
$temp1 = '';
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$temp1[$i] = random_int(0, 99999999);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $restLength; $i++) {
|
||||
if (Crypt::timingSafeCompare($temp1[$i], $code)) {
|
||||
$otherResult = $otherResult || true;
|
||||
$newArray[] = '';
|
||||
$dummyArray[] = $temp1[$i];
|
||||
} else {
|
||||
$otherResult = $otherResult || false;
|
||||
$newArray[] = '';
|
||||
$dummyArray[] = $temp1[$i];
|
||||
}
|
||||
}
|
||||
|
||||
// This last check makes sure than an empty code does not validate
|
||||
$result = $result && !hash_equals('', $code);
|
||||
|
||||
// Save the backup codes
|
||||
$this->saveBackupCodes($newArray, $user);
|
||||
|
||||
// Finally return the result
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
450
administrator/components/com_users/src/Model/CaptiveModel.php
Normal file
450
administrator/components/com_users/src/Model/CaptiveModel.php
Normal file
@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Event\Module;
|
||||
use Joomla\CMS\Event\MultiFactor\Captive;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\DataShape\CaptiveRenderOptions;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Captive Multi-factor Authentication page's model
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class CaptiveModel extends BaseDatabaseModel
|
||||
{
|
||||
/**
|
||||
* Cache of the names of the currently active MFA Methods
|
||||
*
|
||||
* @var array|null
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $activeMFAMethodNames = null;
|
||||
|
||||
/**
|
||||
* Prevents Joomla from displaying any modules.
|
||||
*
|
||||
* This is implemented with a trick. If you use jdoc tags to load modules the JDocumentRendererHtmlModules
|
||||
* uses JModuleHelper::getModules() to load the list of modules to render. This goes through JModuleHelper::load()
|
||||
* which triggers the onAfterModuleList event after cleaning up the module list from duplicates. By resetting
|
||||
* the list to an empty array we force Joomla to not display any modules.
|
||||
*
|
||||
* Similar code paths are followed by any canonical code which tries to load modules. So even if your template does
|
||||
* not use jdoc tags this code will still work as expected.
|
||||
*
|
||||
* @param CMSApplication|null $app The CMS application to manipulate
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function suppressAllModules(CMSApplication $app = null): void
|
||||
{
|
||||
if (\is_null($app)) {
|
||||
$app = Factory::getApplication();
|
||||
}
|
||||
|
||||
$app->registerEvent('onAfterModuleList', [$this, 'onAfterModuleList']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MFA records for the user which correspond to active plugins
|
||||
*
|
||||
* @param User|null $user The user for which to fetch records. Skip to use the current user.
|
||||
* @param bool $includeBackupCodes Should I include the backup codes record?
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getRecords(User $user = null, bool $includeBackupCodes = false): array
|
||||
{
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
// Get the user's MFA records
|
||||
$records = MfaHelper::getUserMfaRecords($user->id);
|
||||
|
||||
// No MFA Methods? Then we obviously don't need to display a Captive login page.
|
||||
if (empty($records)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the enabled MFA Methods' names
|
||||
$methodNames = $this->getActiveMethodNames();
|
||||
|
||||
// Filter the records based on currently active MFA Methods
|
||||
$ret = [];
|
||||
|
||||
$methodNames[] = 'backupcodes';
|
||||
$methodNames = array_unique($methodNames);
|
||||
|
||||
if (!$includeBackupCodes) {
|
||||
$methodNames = array_filter(
|
||||
$methodNames,
|
||||
function ($method) {
|
||||
return $method != 'backupcodes';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
// Backup codes must not be included in the list. We add them in the View, at the end of the list.
|
||||
if (\in_array($record->method, $methodNames)) {
|
||||
$ret[$record->id] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the active MFA Methods' names
|
||||
*
|
||||
* @return array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function getActiveMethodNames(): ?array
|
||||
{
|
||||
if (!\is_null($this->activeMFAMethodNames)) {
|
||||
return $this->activeMFAMethodNames;
|
||||
}
|
||||
|
||||
// Let's get a list of all currently active MFA Methods
|
||||
$mfaMethods = MfaHelper::getMfaMethods();
|
||||
|
||||
// If no MFA Method is active we can't really display a Captive login page.
|
||||
if (empty($mfaMethods)) {
|
||||
$this->activeMFAMethodNames = [];
|
||||
|
||||
return $this->activeMFAMethodNames;
|
||||
}
|
||||
|
||||
// Get a list of just the Method names
|
||||
$this->activeMFAMethodNames = [];
|
||||
|
||||
foreach ($mfaMethods as $mfaMethod) {
|
||||
$this->activeMFAMethodNames[] = $mfaMethod['name'];
|
||||
}
|
||||
|
||||
return $this->activeMFAMethodNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected MFA record for the current user. If the record ID is empty, it does not correspond to
|
||||
* the currently logged in user or does not correspond to an active plugin null is returned instead.
|
||||
*
|
||||
* @param User|null $user The user for which to fetch records. Skip to use the current user.
|
||||
*
|
||||
* @return MfaTable|null
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getRecord(?User $user = null): ?MfaTable
|
||||
{
|
||||
$id = (int) $this->getState('record_id', null);
|
||||
|
||||
if ($id <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
/** @var MfaTable $record */
|
||||
$record = $this->getTable('Mfa', 'Administrator');
|
||||
$loaded = $record->load(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'id' => $id,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$methodNames = $this->getActiveMethodNames();
|
||||
|
||||
if (!\in_array($record->method, $methodNames) && ($record->method != 'backupcodes')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the Captive login page render options for a specific MFA record
|
||||
*
|
||||
* @param MfaTable $record The MFA record to process
|
||||
*
|
||||
* @return CaptiveRenderOptions The rendering options
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function loadCaptiveRenderOptions(?MfaTable $record): CaptiveRenderOptions
|
||||
{
|
||||
$renderOptions = new CaptiveRenderOptions();
|
||||
|
||||
if (empty($record)) {
|
||||
return $renderOptions;
|
||||
}
|
||||
|
||||
$event = new Captive($record);
|
||||
$results = Factory::getApplication()
|
||||
->getDispatcher()
|
||||
->dispatch($event->getName(), $event)
|
||||
->getArgument('result', []);
|
||||
|
||||
if (empty($results)) {
|
||||
if ($record->method === 'backupcodes') {
|
||||
return $renderOptions->merge(
|
||||
[
|
||||
'pre_message' => Text::_('COM_USERS_USER_BACKUPCODES_CAPTIVE_PROMPT'),
|
||||
'input_type' => 'number',
|
||||
'label' => Text::_('COM_USERS_USER_BACKUPCODE'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $renderOptions;
|
||||
}
|
||||
|
||||
foreach ($results as $result) {
|
||||
if (empty($result)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $renderOptions->merge($result);
|
||||
}
|
||||
|
||||
return $renderOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title to display in the Captive login page, or an empty string if no title is to be displayed.
|
||||
*
|
||||
* @return string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getPageTitle(): string
|
||||
{
|
||||
// In the frontend we can choose if we will display a title
|
||||
$showTitle = (bool) ComponentHelper::getParams('com_users')
|
||||
->get('frontend_show_title', 1);
|
||||
|
||||
if (!$showTitle) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Text::_('COM_USERS_USER_MULTIFACTOR_AUTH');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a MFA Method's name into its human-readable, display name
|
||||
*
|
||||
* @param string $name The internal MFA Method name
|
||||
*
|
||||
* @return string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function translateMethodName(string $name): string
|
||||
{
|
||||
static $map = null;
|
||||
|
||||
if (!\is_array($map)) {
|
||||
$map = [];
|
||||
$mfaMethods = MfaHelper::getMfaMethods();
|
||||
|
||||
if (!empty($mfaMethods)) {
|
||||
foreach ($mfaMethods as $mfaMethod) {
|
||||
$map[$mfaMethod['name']] = $mfaMethod['display'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($name == 'backupcodes') {
|
||||
return Text::_('COM_USERS_USER_BACKUPCODES');
|
||||
}
|
||||
|
||||
return $map[$name] ?? $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a MFA Method's name into the relative URL if its logo image
|
||||
*
|
||||
* @param string $name The internal MFA Method name
|
||||
*
|
||||
* @return string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getMethodImage(string $name): string
|
||||
{
|
||||
static $map = null;
|
||||
|
||||
if (!\is_array($map)) {
|
||||
$map = [];
|
||||
$mfaMethods = MfaHelper::getMfaMethods();
|
||||
|
||||
if (!empty($mfaMethods)) {
|
||||
foreach ($mfaMethods as $mfaMethod) {
|
||||
$map[$mfaMethod['name']] = $mfaMethod['image'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($name == 'backupcodes') {
|
||||
return 'media/com_users/images/emergency.svg';
|
||||
}
|
||||
|
||||
return $map[$name] ?? $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the modules list on Joomla! 4.
|
||||
*
|
||||
* Joomla! 4.x is passing an Event object. The first argument of the event object is the array of modules. After
|
||||
* filtering it we have to overwrite the event argument (NOT just return the new list of modules). If a future
|
||||
* version of Joomla! uses immutable events we'll have to use Reflection to do that or Joomla! would have to fix
|
||||
* the way this event is handled, taking its return into account. For now, we just abuse the mutable event
|
||||
* properties - a feature of the event objects we discussed in the Joomla! 4 Working Group back in August 2015.
|
||||
*
|
||||
* @param Module\AfterModuleListEvent $event The Joomla! event object
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function onAfterModuleList(Module\AfterModuleListEvent $event): void
|
||||
{
|
||||
$modules = $event->getModules();
|
||||
|
||||
if (empty($modules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->filterModules($modules);
|
||||
$event->updateModules($modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the Method which actually filters the sites modules based on the allowed module positions specified by
|
||||
* the user.
|
||||
*
|
||||
* @param array $modules The list of the site's modules. Passed by reference.
|
||||
*
|
||||
* @return void The by-reference value is modified instead.
|
||||
* @since 4.2.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function filterModules(array &$modules): void
|
||||
{
|
||||
$allowedPositions = $this->getAllowedModulePositions();
|
||||
|
||||
if (empty($allowedPositions)) {
|
||||
$modules = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
|
||||
foreach ($modules as $module) {
|
||||
if (\in_array($module->position, $allowedPositions)) {
|
||||
$filtered[] = $module;
|
||||
}
|
||||
}
|
||||
|
||||
$modules = $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of module positions we are allowed to display
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function getAllowedModulePositions(): array
|
||||
{
|
||||
$isAdmin = Factory::getApplication()->isClient('administrator');
|
||||
|
||||
// Load the list of allowed module positions from the component's settings. May be different for front- and back-end
|
||||
$configKey = 'allowed_positions_' . ($isAdmin ? 'backend' : 'frontend');
|
||||
$res = ComponentHelper::getParams('com_users')->get($configKey, []);
|
||||
|
||||
// In the backend we must always add the 'title' module position
|
||||
if ($isAdmin) {
|
||||
$res[] = 'title';
|
||||
$res[] = 'toolbar';
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if the mfa method in question has reached it's usage limit
|
||||
*
|
||||
* @param MfaTable $method Mfa method record
|
||||
*
|
||||
* @return boolean true if user can use the method, false if not
|
||||
*
|
||||
* @since 4.3.2
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function checkTryLimit(MfaTable $method)
|
||||
{
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
$jNow = Date::getInstance();
|
||||
$maxTries = (int) $params->get('mfatrycount', 10);
|
||||
$blockHours = (int) $params->get('mfatrytime', 1);
|
||||
|
||||
$lastTryTime = strtotime($method->last_try) ?: 0;
|
||||
$hoursSinceLastTry = (strtotime(Factory::getDate()->toSql()) - $lastTryTime) / 3600;
|
||||
|
||||
if ($method->last_try !== null && $hoursSinceLastTry > $blockHours) {
|
||||
// If it's been long enough, start a new reset count
|
||||
$method->last_try = null;
|
||||
$method->tries = 0;
|
||||
} elseif ($method->tries < $maxTries) {
|
||||
// If we are under the max count, just increment the counter
|
||||
++$method->tries;
|
||||
$method->last_try = $jNow->toSql();
|
||||
} else {
|
||||
// At this point, we know we have exceeded the maximum resets for the time period
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store changes to try counter and/or the timestamp
|
||||
$method->store();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
271
administrator/components/com_users/src/Model/DebuggroupModel.php
Normal file
271
administrator/components/com_users/src/Model/DebuggroupModel.php
Normal file
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\CMS\Object\CMSObject;
|
||||
use Joomla\Component\Users\Administrator\Helper\DebugHelper;
|
||||
use Joomla\Database\DatabaseQuery;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Methods supporting a list of User ACL permissions
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class DebuggroupModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
if (empty($config['filter_fields'])) {
|
||||
$config['filter_fields'] = [
|
||||
'a.title',
|
||||
'component', 'a.name',
|
||||
'a.lft',
|
||||
'a.id',
|
||||
'level_start', 'level_end', 'a.level',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the actions.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getDebugActions()
|
||||
{
|
||||
$component = $this->getState('filter.component');
|
||||
|
||||
return DebugHelper::getDebugActions($component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getItems method.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getItems()
|
||||
{
|
||||
$groupId = $this->getState('group_id');
|
||||
|
||||
if (($assets = parent::getItems()) && $groupId) {
|
||||
$actions = $this->getDebugActions();
|
||||
|
||||
foreach ($assets as &$asset) {
|
||||
$asset->checks = [];
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$name = $action[0];
|
||||
$asset->checks[$name] = Access::checkGroup($groupId, $name, $asset->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @param string $ordering An optional ordering field.
|
||||
* @param string $direction An optional direction (asc|desc).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function populateState($ordering = 'a.lft', $direction = 'asc')
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Adjust the context to support modal layouts.
|
||||
$layout = $app->getInput()->get('layout', 'default');
|
||||
|
||||
if ($layout) {
|
||||
$this->context .= '.' . $layout;
|
||||
}
|
||||
|
||||
// Load the filter state.
|
||||
$this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
|
||||
$this->setState('group_id', $this->getUserStateFromRequest($this->context . '.group_id', 'group_id', 0, 'int', false));
|
||||
|
||||
$levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd');
|
||||
$this->setState('filter.level_start', $levelStart);
|
||||
|
||||
$value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd');
|
||||
|
||||
if ($value > 0 && $value < $levelStart) {
|
||||
$value = $levelStart;
|
||||
}
|
||||
|
||||
$this->setState('filter.level_end', $value);
|
||||
|
||||
$this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string'));
|
||||
|
||||
// Load the parameters.
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
$this->setState('params', $params);
|
||||
|
||||
// List state information.
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a store id based on model configuration state.
|
||||
*
|
||||
* This is necessary because the model is used by the component and
|
||||
* different modules that might need different sets of data or different
|
||||
* ordering requirements.
|
||||
*
|
||||
* @param string $id A prefix for the store id.
|
||||
*
|
||||
* @return string A store id.
|
||||
*/
|
||||
protected function getStoreId($id = '')
|
||||
{
|
||||
// Compile the store id.
|
||||
$id .= ':' . $this->getState('group_id');
|
||||
$id .= ':' . $this->getState('filter.search');
|
||||
$id .= ':' . $this->getState('filter.level_start');
|
||||
$id .= ':' . $this->getState('filter.level_end');
|
||||
$id .= ':' . $this->getState('filter.component');
|
||||
|
||||
return parent::getStoreId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the group being debugged.
|
||||
*
|
||||
* @return CMSObject
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getGroup()
|
||||
{
|
||||
$groupId = (int) $this->getState('group_id');
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['id', 'title']))
|
||||
->from($db->quoteName('#__usergroups'))
|
||||
->where($db->quoteName('id') . ' = :id')
|
||||
->bind(':id', $groupId, ParameterType::INTEGER);
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$group = $db->loadObject();
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return DatabaseQuery
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function getListQuery()
|
||||
{
|
||||
// Create a new query object.
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Select the required fields from the table.
|
||||
$query->select(
|
||||
$this->getState(
|
||||
'list.select',
|
||||
'a.id, a.name, a.title, a.level, a.lft, a.rgt'
|
||||
)
|
||||
);
|
||||
$query->from($db->quoteName('#__assets', 'a'));
|
||||
|
||||
// Filter the items over the search string if set.
|
||||
if ($this->getState('filter.search')) {
|
||||
$search = '%' . trim($this->getState('filter.search')) . '%';
|
||||
|
||||
// Add the clauses to the query.
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.name') . ' LIKE :name'
|
||||
. ' OR ' . $db->quoteName('a.title') . ' LIKE :title)'
|
||||
)
|
||||
->bind(':name', $search)
|
||||
->bind(':title', $search);
|
||||
}
|
||||
|
||||
// Filter on the start and end levels.
|
||||
$levelStart = (int) $this->getState('filter.level_start');
|
||||
$levelEnd = (int) $this->getState('filter.level_end');
|
||||
|
||||
if ($levelEnd > 0 && $levelEnd < $levelStart) {
|
||||
$levelEnd = $levelStart;
|
||||
}
|
||||
|
||||
if ($levelStart > 0) {
|
||||
$query->where($db->quoteName('a.level') . ' >= :levelStart')
|
||||
->bind(':levelStart', $levelStart, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
if ($levelEnd > 0) {
|
||||
$query->where($db->quoteName('a.level') . ' <= :levelEnd')
|
||||
->bind(':levelEnd', $levelEnd, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
// Filter the items over the component if set.
|
||||
if ($this->getState('filter.component')) {
|
||||
$component = $this->getState('filter.component');
|
||||
$lcomponent = $component . '.%';
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.name') . ' = :component'
|
||||
. ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)'
|
||||
)
|
||||
->bind(':component', $component)
|
||||
->bind(':lcomponent', $lcomponent);
|
||||
}
|
||||
|
||||
// Add the list ordering clause.
|
||||
$query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
259
administrator/components/com_users/src/Model/DebuguserModel.php
Normal file
259
administrator/components/com_users/src/Model/DebuguserModel.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\CMS\User\UserFactoryAwareInterface;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Helper\DebugHelper;
|
||||
use Joomla\Database\DatabaseQuery;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Methods supporting a list of User ACL permissions
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class DebuguserModel extends ListModel implements UserFactoryAwareInterface
|
||||
{
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
if (empty($config['filter_fields'])) {
|
||||
$config['filter_fields'] = [
|
||||
'a.title',
|
||||
'component', 'a.name',
|
||||
'a.lft',
|
||||
'a.id',
|
||||
'level_start', 'level_end', 'a.level',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the actions.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getDebugActions()
|
||||
{
|
||||
$component = $this->getState('filter.component');
|
||||
|
||||
return DebugHelper::getDebugActions($component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getItems method.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getItems()
|
||||
{
|
||||
$userId = $this->getState('user_id');
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
if (($assets = parent::getItems()) && $userId) {
|
||||
$actions = $this->getDebugActions();
|
||||
|
||||
foreach ($assets as &$asset) {
|
||||
$asset->checks = [];
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$name = $action[0];
|
||||
$asset->checks[$name] = $user->authorise($name, $asset->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @param string $ordering An optional ordering field.
|
||||
* @param string $direction An optional direction (asc|desc).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function populateState($ordering = 'a.lft', $direction = 'asc')
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Adjust the context to support modal layouts.
|
||||
$layout = $app->getInput()->get('layout', 'default');
|
||||
|
||||
if ($layout) {
|
||||
$this->context .= '.' . $layout;
|
||||
}
|
||||
|
||||
// Load the filter state.
|
||||
$this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
|
||||
$this->setState('user_id', $this->getUserStateFromRequest($this->context . '.user_id', 'user_id', 0, 'int', false));
|
||||
|
||||
$levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd');
|
||||
$this->setState('filter.level_start', $levelStart);
|
||||
|
||||
$value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd');
|
||||
|
||||
if ($value > 0 && $value < $levelStart) {
|
||||
$value = $levelStart;
|
||||
}
|
||||
|
||||
$this->setState('filter.level_end', $value);
|
||||
|
||||
$this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string'));
|
||||
|
||||
// Load the parameters.
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
$this->setState('params', $params);
|
||||
|
||||
// List state information.
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a store id based on model configuration state.
|
||||
*
|
||||
* This is necessary because the model is used by the component and
|
||||
* different modules that might need different sets of data or different
|
||||
* ordering requirements.
|
||||
*
|
||||
* @param string $id A prefix for the store id.
|
||||
*
|
||||
* @return string A store id.
|
||||
*/
|
||||
protected function getStoreId($id = '')
|
||||
{
|
||||
// Compile the store id.
|
||||
$id .= ':' . $this->getState('user_id');
|
||||
$id .= ':' . $this->getState('filter.search');
|
||||
$id .= ':' . $this->getState('filter.level_start');
|
||||
$id .= ':' . $this->getState('filter.level_end');
|
||||
$id .= ':' . $this->getState('filter.component');
|
||||
|
||||
return parent::getStoreId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user being debugged.
|
||||
*
|
||||
* @return User
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
$userId = $this->getState('user_id');
|
||||
|
||||
return $this->getUserFactory()->loadUserById($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return DatabaseQuery
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function getListQuery()
|
||||
{
|
||||
// Create a new query object.
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Select the required fields from the table.
|
||||
$query->select(
|
||||
$this->getState(
|
||||
'list.select',
|
||||
'a.id, a.name, a.title, a.level, a.lft, a.rgt'
|
||||
)
|
||||
);
|
||||
$query->from($db->quoteName('#__assets', 'a'));
|
||||
|
||||
// Filter the items over the search string if set.
|
||||
if ($this->getState('filter.search')) {
|
||||
$search = '%' . trim($this->getState('filter.search')) . '%';
|
||||
|
||||
// Add the clauses to the query.
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.name') . ' LIKE :name'
|
||||
. ' OR ' . $db->quoteName('a.title') . ' LIKE :title)'
|
||||
)
|
||||
->bind(':name', $search)
|
||||
->bind(':title', $search);
|
||||
}
|
||||
|
||||
// Filter on the start and end levels.
|
||||
$levelStart = (int) $this->getState('filter.level_start');
|
||||
$levelEnd = (int) $this->getState('filter.level_end');
|
||||
|
||||
if ($levelEnd > 0 && $levelEnd < $levelStart) {
|
||||
$levelEnd = $levelStart;
|
||||
}
|
||||
|
||||
if ($levelStart > 0) {
|
||||
$query->where($db->quoteName('a.level') . ' >= :levelStart')
|
||||
->bind(':levelStart', $levelStart, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
if ($levelEnd > 0) {
|
||||
$query->where($db->quoteName('a.level') . ' <= :levelEnd')
|
||||
->bind(':levelEnd', $levelEnd, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
// Filter the items over the component if set.
|
||||
if ($this->getState('filter.component')) {
|
||||
$component = $this->getState('filter.component');
|
||||
$lcomponent = $component . '.%';
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.name') . ' = :component'
|
||||
. ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)'
|
||||
)
|
||||
->bind(':component', $component)
|
||||
->bind(':lcomponent', $lcomponent);
|
||||
}
|
||||
|
||||
// Add the list ordering clause.
|
||||
$query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
347
administrator/components/com_users/src/Model/GroupModel.php
Normal file
347
administrator/components/com_users/src/Model/GroupModel.php
Normal file
@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Event\User\UserGroupAfterDeleteEvent;
|
||||
use Joomla\CMS\Event\User\UserGroupBeforeDeleteEvent;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Form\Form;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\AdminModel;
|
||||
use Joomla\CMS\Object\CMSObject;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Table\Table;
|
||||
use Joomla\String\StringHelper;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User group model.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class GroupModel extends AdminModel
|
||||
{
|
||||
/**
|
||||
* Override parent constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
$config = array_merge(
|
||||
[
|
||||
'event_after_delete' => 'onUserAfterDeleteGroup',
|
||||
'event_after_save' => 'onUserAfterSaveGroup',
|
||||
'event_before_delete' => 'onUserBeforeDeleteGroup',
|
||||
'event_before_save' => 'onUserBeforeSaveGroup',
|
||||
'events_map' => ['delete' => 'user', 'save' => 'user'],
|
||||
],
|
||||
$config
|
||||
);
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the a Table object, always creating it.
|
||||
*
|
||||
* @param string $type The table type to instantiate
|
||||
* @param string $prefix A prefix for the table class name. Optional.
|
||||
* @param array $config Configuration array for model. Optional.
|
||||
*
|
||||
* @return Table A database object
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getTable($type = 'Usergroup', $prefix = 'Joomla\\CMS\\Table\\', $config = [])
|
||||
{
|
||||
$return = Table::getInstance($type, $prefix, $config);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the record form.
|
||||
*
|
||||
* @param array $data An optional array of data for the form to interrogate.
|
||||
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
|
||||
*
|
||||
* @return Form|bool A Form object on success, false on failure
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getForm($data = [], $loadData = true)
|
||||
{
|
||||
// Get the form.
|
||||
$form = $this->loadForm('com_users.group', 'group', ['control' => 'jform', 'load_data' => $loadData]);
|
||||
|
||||
if (empty($form)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the data that should be injected in the form.
|
||||
*
|
||||
* @return mixed The data for the form.
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function loadFormData()
|
||||
{
|
||||
// Check the session for previously entered form data.
|
||||
$data = Factory::getApplication()->getUserState('com_users.edit.group.data', []);
|
||||
|
||||
if (empty($data)) {
|
||||
$data = $this->getItem();
|
||||
}
|
||||
|
||||
$this->preprocessData('com_users.group', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override preprocessForm to load the user plugin group instead of content.
|
||||
*
|
||||
* @param Form $form A form object.
|
||||
* @param mixed $data The data expected for the form.
|
||||
* @param string $group The name of the plugin group to import (defaults to "content").
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception if there is an error loading the form.
|
||||
*/
|
||||
protected function preprocessForm(Form $form, $data, $group = '')
|
||||
{
|
||||
$obj = \is_array($data) ? ArrayHelper::toObject($data, CMSObject::class) : $data;
|
||||
|
||||
if (isset($obj->parent_id) && $obj->parent_id == 0 && $obj->id > 0) {
|
||||
$form->setFieldAttribute('parent_id', 'type', 'hidden');
|
||||
$form->setFieldAttribute('parent_id', 'hidden', 'true');
|
||||
}
|
||||
|
||||
parent::preprocessForm($form, $data, 'user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to save the form data.
|
||||
*
|
||||
* @param array $data The form data.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function save($data)
|
||||
{
|
||||
// Include the user plugins for events.
|
||||
PluginHelper::importPlugin($this->events_map['save']);
|
||||
|
||||
/**
|
||||
* Check the super admin permissions for group
|
||||
* We get the parent group permissions and then check the group permissions manually
|
||||
* We have to calculate the group permissions manually because we haven't saved the group yet
|
||||
*/
|
||||
$parentSuperAdmin = Access::checkGroup($data['parent_id'], 'core.admin');
|
||||
|
||||
// Get core.admin rules from the root asset
|
||||
$rules = Access::getAssetRules('root.1')->getData();
|
||||
|
||||
// Get the value for the current group (will be true (allowed), false (denied), or null (inherit)
|
||||
$groupSuperAdmin = $rules['core.admin']->allow($data['id']);
|
||||
|
||||
// We only need to change the $groupSuperAdmin if the parent is true or false. Otherwise, the value set in the rule takes effect.
|
||||
if ($parentSuperAdmin === false) {
|
||||
// If parent is false (Denied), effective value will always be false
|
||||
$groupSuperAdmin = false;
|
||||
} elseif ($parentSuperAdmin === true) {
|
||||
// If parent is true (allowed), group is true unless explicitly set to false
|
||||
$groupSuperAdmin = ($groupSuperAdmin === false) ? false : true;
|
||||
}
|
||||
|
||||
// Check for non-super admin trying to save with super admin group
|
||||
$iAmSuperAdmin = $this->getCurrentUser()->authorise('core.admin');
|
||||
|
||||
if (!$iAmSuperAdmin && $groupSuperAdmin) {
|
||||
$this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for super-admin changing self to be non-super-admin
|
||||
* First, are we a super admin
|
||||
*/
|
||||
if ($iAmSuperAdmin) {
|
||||
// Next, are we a member of the current group?
|
||||
$myGroups = Access::getGroupsByUser($this->getCurrentUser()->get('id'), false);
|
||||
|
||||
if (\in_array($data['id'], $myGroups)) {
|
||||
// Now, would we have super admin permissions without the current group?
|
||||
$otherGroups = array_diff($myGroups, [$data['id']]);
|
||||
$otherSuperAdmin = false;
|
||||
|
||||
foreach ($otherGroups as $otherGroup) {
|
||||
$otherSuperAdmin = $otherSuperAdmin ?: Access::checkGroup($otherGroup, 'core.admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* If we would not otherwise have super admin permissions
|
||||
* and the current group does not have super admin permissions, throw an exception
|
||||
*/
|
||||
if ((!$otherSuperAdmin) && (!$groupSuperAdmin)) {
|
||||
$this->setError(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Factory::getApplication()->getInput()->get('task') == 'save2copy') {
|
||||
$data['title'] = $this->generateGroupTitle($data['parent_id'], $data['title']);
|
||||
}
|
||||
|
||||
// Proceed with the save
|
||||
return parent::save($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to delete rows.
|
||||
*
|
||||
* @param array &$pks An array of item ids.
|
||||
*
|
||||
* @return boolean Returns true on success, false on failure.
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete(&$pks)
|
||||
{
|
||||
// Typecast variable.
|
||||
$pks = (array) $pks;
|
||||
$user = $this->getCurrentUser();
|
||||
$groups = Access::getGroupsByUser($user->get('id'));
|
||||
$context = $this->option . '.' . $this->name;
|
||||
$dispatcher = $this->getDispatcher();
|
||||
|
||||
// Get a row instance.
|
||||
$table = $this->getTable();
|
||||
|
||||
// Load plugins.
|
||||
PluginHelper::importPlugin($this->events_map['delete'], null, true, $dispatcher);
|
||||
|
||||
// Check if I am a Super Admin
|
||||
$iAmSuperAdmin = $user->authorise('core.admin');
|
||||
|
||||
foreach ($pks as $pk) {
|
||||
// Do not allow to delete groups to which the current user belongs
|
||||
if (\in_array($pk, $groups)) {
|
||||
Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_DELETE_ERROR_INVALID_GROUP'), 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$table->load($pk)) {
|
||||
// Item is not in the table.
|
||||
$this->setError($table->getError());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate the items to delete each one.
|
||||
foreach ($pks as $i => $pk) {
|
||||
if ($table->load($pk)) {
|
||||
// Access checks.
|
||||
$allow = $user->authorise('core.edit.state', 'com_users');
|
||||
|
||||
// Don't allow non-super-admin to delete a super admin
|
||||
$allow = (!$iAmSuperAdmin && Access::checkGroup($pk, 'core.admin')) ? false : $allow;
|
||||
|
||||
if ($allow) {
|
||||
// Fire the before delete event.
|
||||
$beforeDeleteEvent = new UserGroupBeforeDeleteEvent($this->event_before_delete, [
|
||||
'data' => $table->getProperties(), // @TODO: Remove data argument in Joomla 6, see UserGroupBeforeDeleteEvent
|
||||
'context' => $context,
|
||||
'subject' => $table,
|
||||
]);
|
||||
$result = $dispatcher->dispatch($this->event_before_delete, $beforeDeleteEvent)->getArgument('result', []);
|
||||
|
||||
if (\in_array(false, $result, true)) {
|
||||
$this->setError($table->getError());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$table->delete($pk)) {
|
||||
$this->setError($table->getError());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trigger the after delete event.
|
||||
$dispatcher->dispatch($this->event_after_delete, new UserGroupAfterDeleteEvent($this->event_after_delete, [
|
||||
'data' => $table->getProperties(), // @TODO: Remove data argument in Joomla 6, see UserGroupAfterDeleteEvent
|
||||
'deletingResult' => true, // @TODO: Remove deletingResult argument in Joomla 6, see UserGroupAfterDeleteEvent
|
||||
'errorMessage' => $this->getError(), // @TODO: Remove errorMessage argument in Joomla 6, see UserGroupAfterDeleteEvent
|
||||
'context' => $context,
|
||||
'subject' => $table,
|
||||
]));
|
||||
} else {
|
||||
// Prune items that you can't change.
|
||||
unset($pks[$i]);
|
||||
Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to generate the title of group on Save as Copy action
|
||||
*
|
||||
* @param integer $parentId The id of the parent.
|
||||
* @param string $title The title of group
|
||||
*
|
||||
* @return string Contains the modified title.
|
||||
*
|
||||
* @since 3.3.7
|
||||
*/
|
||||
protected function generateGroupTitle($parentId, $title)
|
||||
{
|
||||
// Alter the title & alias
|
||||
$table = $this->getTable();
|
||||
|
||||
while ($table->load(['title' => $title, 'parent_id' => $parentId])) {
|
||||
if ($title == $table->title) {
|
||||
$title = StringHelper::increment($title);
|
||||
}
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
246
administrator/components/com_users/src/Model/GroupsModel.php
Normal file
246
administrator/components/com_users/src/Model/GroupsModel.php
Normal file
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Helper\UserGroupsHelper;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\Database\DatabaseQuery;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Methods supporting a list of user group records.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class GroupsModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Override parent constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
if (empty($config['filter_fields'])) {
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'parent_id', 'a.parent_id',
|
||||
'title', 'a.title',
|
||||
'lft', 'a.lft',
|
||||
'rgt', 'a.rgt',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @param string $ordering An optional ordering field.
|
||||
* @param string $direction An optional direction (asc|desc).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function populateState($ordering = 'a.lft', $direction = 'asc')
|
||||
{
|
||||
// Load the parameters.
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
$this->setState('params', $params);
|
||||
|
||||
// List state information.
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a store id based on model configuration state.
|
||||
*
|
||||
* This is necessary because the model is used by the component and
|
||||
* different modules that might need different sets of data or different
|
||||
* ordering requirements.
|
||||
*
|
||||
* @param string $id A prefix for the store id.
|
||||
*
|
||||
* @return string A store id.
|
||||
*/
|
||||
protected function getStoreId($id = '')
|
||||
{
|
||||
// Compile the store id.
|
||||
$id .= ':' . $this->getState('filter.search');
|
||||
|
||||
return parent::getStoreId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of groups and adds expensive joins to the result set.
|
||||
*
|
||||
* @return mixed An array of data items on success, false on failure.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getItems()
|
||||
{
|
||||
// Get a storage key.
|
||||
$store = $this->getStoreId();
|
||||
|
||||
// Try to load the data from internal storage.
|
||||
if (empty($this->cache[$store])) {
|
||||
$items = parent::getItems();
|
||||
|
||||
// Bail out on an error or empty list.
|
||||
if (empty($items)) {
|
||||
$this->cache[$store] = $items;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
try {
|
||||
$items = $this->populateExtraData($items);
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the items to the internal cache.
|
||||
$this->cache[$store] = $items;
|
||||
}
|
||||
|
||||
return $this->cache[$store];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return DatabaseQuery
|
||||
*/
|
||||
protected function getListQuery()
|
||||
{
|
||||
// Create a new query object.
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Select the required fields from the table.
|
||||
$query->select(
|
||||
$this->getState(
|
||||
'list.select',
|
||||
'a.*'
|
||||
)
|
||||
);
|
||||
$query->from($db->quoteName('#__usergroups') . ' AS a');
|
||||
|
||||
// Filter the comments over the search string if set.
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search)) {
|
||||
if (stripos($search, 'id:') === 0) {
|
||||
$ids = (int) substr($search, 3);
|
||||
$query->where($db->quoteName('a.id') . ' = :id');
|
||||
$query->bind(':id', $ids, ParameterType::INTEGER);
|
||||
} else {
|
||||
$search = '%' . trim($search) . '%';
|
||||
$query->where($db->quoteName('a.title') . ' LIKE :title');
|
||||
$query->bind(':title', $search);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the list ordering clause.
|
||||
$query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate level & path for items.
|
||||
*
|
||||
* @param array $items Array of \stdClass objects
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
private function populateExtraData(array $items)
|
||||
{
|
||||
// First pass: get list of the group ids and reset the counts.
|
||||
$groupsByKey = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$groupsByKey[(int) $item->id] = $item;
|
||||
}
|
||||
|
||||
$groupIds = array_keys($groupsByKey);
|
||||
|
||||
$db = $this->getDatabase();
|
||||
|
||||
// Get total enabled users in group.
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Count the objects in the user group.
|
||||
$query->select('map.group_id, COUNT(DISTINCT map.user_id) AS user_count')
|
||||
->from($db->quoteName('#__user_usergroup_map', 'map'))
|
||||
->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id'))
|
||||
->whereIn($db->quoteName('map.group_id'), $groupIds)
|
||||
->where($db->quoteName('u.block') . ' = 0')
|
||||
->group($db->quoteName('map.group_id'));
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$countEnabled = $db->loadAssocList('group_id', 'count_enabled');
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get total disabled users in group.
|
||||
$query->clear();
|
||||
$query->select('map.group_id, COUNT(DISTINCT map.user_id) AS user_count')
|
||||
->from($db->quoteName('#__user_usergroup_map', 'map'))
|
||||
->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id'))
|
||||
->whereIn($db->quoteName('map.group_id'), $groupIds)
|
||||
->where($db->quoteName('u.block') . ' = 1')
|
||||
->group($db->quoteName('map.group_id'));
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$countDisabled = $db->loadAssocList('group_id', 'count_disabled');
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inject the values back into the array.
|
||||
foreach ($groupsByKey as &$item) {
|
||||
$item->count_enabled = isset($countEnabled[$item->id]) ? (int) $countEnabled[$item->id]['user_count'] : 0;
|
||||
$item->count_disabled = isset($countDisabled[$item->id]) ? (int) $countDisabled[$item->id]['user_count'] : 0;
|
||||
$item->user_count = $item->count_enabled + $item->count_disabled;
|
||||
}
|
||||
|
||||
$groups = new UserGroupsHelper($groupsByKey);
|
||||
|
||||
return array_values($groups->getAll());
|
||||
}
|
||||
}
|
||||
297
administrator/components/com_users/src/Model/LevelModel.php
Normal file
297
administrator/components/com_users/src/Model/LevelModel.php
Normal file
@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Form\Form;
|
||||
use Joomla\CMS\Helper\UserGroupsHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Model\AdminModel;
|
||||
use Joomla\CMS\Table\Table;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User view level model.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class LevelModel extends AdminModel
|
||||
{
|
||||
/**
|
||||
* @var array A list of the access levels in use.
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $levelsInUse = null;
|
||||
|
||||
/**
|
||||
* Method to test whether a record can be deleted.
|
||||
*
|
||||
* @param object $record A record object.
|
||||
*
|
||||
* @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function canDelete($record)
|
||||
{
|
||||
$groups = json_decode($record->rules);
|
||||
|
||||
if ($groups === null) {
|
||||
throw new \RuntimeException('Invalid rules schema');
|
||||
}
|
||||
|
||||
$isAdmin = $this->getCurrentUser()->authorise('core.admin');
|
||||
|
||||
// Check permissions
|
||||
foreach ($groups as $group) {
|
||||
if (!$isAdmin && Access::checkGroup($group, 'core.admin')) {
|
||||
$this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the access level is being used by any content.
|
||||
if ($this->levelsInUse === null) {
|
||||
// Populate the list once.
|
||||
$this->levelsInUse = [];
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select('DISTINCT access');
|
||||
|
||||
// Get all the tables and the prefix
|
||||
$tables = $db->getTableList();
|
||||
$prefix = $db->getPrefix();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
// Get all of the columns in the table
|
||||
$fields = $db->getTableColumns($table);
|
||||
|
||||
/**
|
||||
* We are looking for the access field. If custom tables are using something other
|
||||
* than the 'access' field they are on their own unfortunately.
|
||||
* Also make sure the table prefix matches the live db prefix (eg, it is not a "bak_" table)
|
||||
*/
|
||||
if (strpos($table, $prefix) === 0 && isset($fields['access'])) {
|
||||
// Lookup the distinct values of the field.
|
||||
$query->clear('from')
|
||||
->from($db->quoteName($table));
|
||||
$db->setQuery($query);
|
||||
|
||||
try {
|
||||
$values = $db->loadColumn();
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->levelsInUse = array_merge($this->levelsInUse, $values);
|
||||
|
||||
// @todo Could assemble an array of the tables used by each view level list those,
|
||||
// giving the user a clue in the error where to look.
|
||||
}
|
||||
}
|
||||
|
||||
// Get uniques.
|
||||
$this->levelsInUse = array_unique($this->levelsInUse);
|
||||
|
||||
// Ok, after all that we are ready to check the record :)
|
||||
}
|
||||
|
||||
if (\in_array($record->id, $this->levelsInUse)) {
|
||||
$this->setError(Text::sprintf('COM_USERS_ERROR_VIEW_LEVEL_IN_USE', $record->id, $record->title));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canDelete($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the a Table object, always creating it.
|
||||
*
|
||||
* @param string $type The table type to instantiate
|
||||
* @param string $prefix A prefix for the table class name. Optional.
|
||||
* @param array $config Configuration array for model. Optional.
|
||||
*
|
||||
* @return Table A database object
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getTable($type = 'ViewLevel', $prefix = 'Joomla\\CMS\\Table\\', $config = [])
|
||||
{
|
||||
$return = Table::getInstance($type, $prefix, $config);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a single record.
|
||||
*
|
||||
* @param integer $pk The id of the primary key.
|
||||
*
|
||||
* @return mixed Object on success, false on failure.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getItem($pk = null)
|
||||
{
|
||||
$result = parent::getItem($pk);
|
||||
|
||||
// Convert the params field to an array.
|
||||
$result->rules = $result->rules !== null ? json_decode($result->rules) : [];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the record form.
|
||||
*
|
||||
* @param array $data An optional array of data for the form to interrogate.
|
||||
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
|
||||
*
|
||||
* @return Form|bool A Form object on success, false on failure
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getForm($data = [], $loadData = true)
|
||||
{
|
||||
// Get the form.
|
||||
$form = $this->loadForm('com_users.level', 'level', ['control' => 'jform', 'load_data' => $loadData]);
|
||||
|
||||
if (empty($form)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the data that should be injected in the form.
|
||||
*
|
||||
* @return mixed The data for the form.
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function loadFormData()
|
||||
{
|
||||
// Check the session for previously entered form data.
|
||||
$data = Factory::getApplication()->getUserState('com_users.edit.level.data', []);
|
||||
|
||||
if (empty($data)) {
|
||||
$data = $this->getItem();
|
||||
}
|
||||
|
||||
$this->preprocessData('com_users.level', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to preprocess the form
|
||||
*
|
||||
* @param Form $form A form object.
|
||||
* @param mixed $data The data expected for the form.
|
||||
* @param string $group The name of the plugin group to import (defaults to "content").
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception if there is an error loading the form.
|
||||
*/
|
||||
protected function preprocessForm(Form $form, $data, $group = '')
|
||||
{
|
||||
// TO DO warning!
|
||||
parent::preprocessForm($form, $data, 'user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to save the form data.
|
||||
*
|
||||
* @param array $data The form data.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function save($data)
|
||||
{
|
||||
if (!isset($data['rules'])) {
|
||||
$data['rules'] = [];
|
||||
}
|
||||
|
||||
$data['title'] = InputFilter::getInstance()->clean($data['title'], 'TRIM');
|
||||
|
||||
return parent::save($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to validate the form data.
|
||||
*
|
||||
* @param Form $form The form to validate against.
|
||||
* @param array $data The data to validate.
|
||||
* @param string $group The name of the field group to validate.
|
||||
*
|
||||
* @return array|boolean Array of filtered data if valid, false otherwise.
|
||||
*
|
||||
* @see \Joomla\CMS\Form\FormRule
|
||||
* @see \Joomla\CMS\Filter\InputFilter
|
||||
* @since 3.8.8
|
||||
*/
|
||||
public function validate($form, $data, $group = null)
|
||||
{
|
||||
$isSuperAdmin = $this->getCurrentUser()->authorise('core.admin');
|
||||
|
||||
// Non Super user should not be able to change the access levels of super user groups
|
||||
if (!$isSuperAdmin) {
|
||||
if (!isset($data['rules']) || !\is_array($data['rules'])) {
|
||||
$data['rules'] = [];
|
||||
}
|
||||
|
||||
$groups = array_values(UserGroupsHelper::getInstance()->getAll());
|
||||
|
||||
$rules = [];
|
||||
|
||||
if (!empty($data['id'])) {
|
||||
$table = $this->getTable();
|
||||
|
||||
$table->load($data['id']);
|
||||
|
||||
$rules = json_decode($table->rules);
|
||||
}
|
||||
|
||||
$rules = ArrayHelper::toInteger($rules);
|
||||
|
||||
for ($i = 0, $n = \count($groups); $i < $n; ++$i) {
|
||||
if (Access::checkGroup((int) $groups[$i]->id, 'core.admin')) {
|
||||
if (\in_array((int) $groups[$i]->id, $rules) && !\in_array((int) $groups[$i]->id, $data['rules'])) {
|
||||
$data['rules'][] = (int) $groups[$i]->id;
|
||||
} elseif (!\in_array((int) $groups[$i]->id, $rules) && \in_array((int) $groups[$i]->id, $data['rules'])) {
|
||||
$this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN'));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::validate($form, $data, $group);
|
||||
}
|
||||
}
|
||||
234
administrator/components/com_users/src/Model/LevelsModel.php
Normal file
234
administrator/components/com_users/src/Model/LevelsModel.php
Normal file
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\CMS\Table\Table;
|
||||
use Joomla\Database\DatabaseQuery;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Methods supporting a list of user access level records.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class LevelsModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Override parent constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
if (empty($config['filter_fields'])) {
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'title', 'a.title',
|
||||
'ordering', 'a.ordering',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @param string $ordering An optional ordering field.
|
||||
* @param string $direction An optional direction (asc|desc).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function populateState($ordering = 'a.ordering', $direction = 'asc')
|
||||
{
|
||||
// Load the parameters.
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
$this->setState('params', $params);
|
||||
|
||||
// List state information.
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a store id based on model configuration state.
|
||||
*
|
||||
* This is necessary because the model is used by the component and
|
||||
* different modules that might need different sets of data or different
|
||||
* ordering requirements.
|
||||
*
|
||||
* @param string $id A prefix for the store id.
|
||||
*
|
||||
* @return string A store id.
|
||||
*/
|
||||
protected function getStoreId($id = '')
|
||||
{
|
||||
// Compile the store id.
|
||||
$id .= ':' . $this->getState('filter.search');
|
||||
|
||||
return parent::getStoreId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return DatabaseQuery
|
||||
*/
|
||||
protected function getListQuery()
|
||||
{
|
||||
// Create a new query object.
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Select the required fields from the table.
|
||||
$query->select(
|
||||
$this->getState(
|
||||
'list.select',
|
||||
'a.*'
|
||||
)
|
||||
);
|
||||
$query->from($db->quoteName('#__viewlevels') . ' AS a');
|
||||
|
||||
// Add the level in the tree.
|
||||
$query->group('a.id, a.title, a.ordering, a.rules');
|
||||
|
||||
// Filter the items over the search string if set.
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search)) {
|
||||
if (stripos($search, 'id:') === 0) {
|
||||
$ids = (int) substr($search, 3);
|
||||
$query->where($db->quoteName('a.id') . ' = :id');
|
||||
$query->bind(':id', $ids, ParameterType::INTEGER);
|
||||
} else {
|
||||
$search = '%' . trim($search) . '%';
|
||||
$query->where('a.title LIKE :title')
|
||||
->bind(':title', $search);
|
||||
}
|
||||
}
|
||||
|
||||
$query->group('a.id');
|
||||
|
||||
// Add the list ordering clause.
|
||||
$query->order($db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to adjust the ordering of a row.
|
||||
*
|
||||
* @param integer $pk The ID of the primary key to move.
|
||||
* @param integer $direction Increment, usually +1 or -1
|
||||
*
|
||||
* @return boolean False on failure or error, true otherwise.
|
||||
*/
|
||||
public function reorder($pk, $direction = 0)
|
||||
{
|
||||
// Sanitize the id and adjustment.
|
||||
$pk = (!empty($pk)) ? $pk : (int) $this->getState('level.id');
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
// Get an instance of the record's table.
|
||||
$table = Table::getInstance('ViewLevel', 'Joomla\\CMS\Table\\');
|
||||
|
||||
// Load the row.
|
||||
if (!$table->load($pk)) {
|
||||
$this->setError($table->getError());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Access checks.
|
||||
$allow = $user->authorise('core.edit.state', 'com_users');
|
||||
|
||||
if (!$allow) {
|
||||
$this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move the row.
|
||||
// @todo: Where clause to restrict category.
|
||||
$table->move($pk);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the manually set order of records.
|
||||
*
|
||||
* @param array $pks An array of primary key ids.
|
||||
* @param integer $order Order position
|
||||
*
|
||||
* @return boolean Boolean true on success, boolean false
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function saveorder($pks, $order)
|
||||
{
|
||||
$table = Table::getInstance('viewlevel', 'Joomla\\CMS\Table\\');
|
||||
$user = $this->getCurrentUser();
|
||||
$conditions = [];
|
||||
|
||||
if (empty($pks)) {
|
||||
Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_ERROR_LEVELS_NOLEVELS_SELECTED'), 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update ordering values
|
||||
foreach ($pks as $i => $pk) {
|
||||
$table->load((int) $pk);
|
||||
|
||||
// Access checks.
|
||||
$allow = $user->authorise('core.edit.state', 'com_users');
|
||||
|
||||
if (!$allow) {
|
||||
// Prune items that you can't change.
|
||||
unset($pks[$i]);
|
||||
Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
|
||||
} elseif ($table->ordering != $order[$i]) {
|
||||
$table->ordering = $order[$i];
|
||||
|
||||
if (!$table->store()) {
|
||||
$this->setError($table->getError());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute reorder for each category.
|
||||
foreach ($conditions as $cond) {
|
||||
$table->load($cond[0]);
|
||||
$table->reorder($cond[1]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
246
administrator/components/com_users/src/Model/MailModel.php
Normal file
246
administrator/components/com_users/src/Model/MailModel.php
Normal file
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Form\Form;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Mail\Exception\MailDisabledException;
|
||||
use Joomla\CMS\Mail\MailTemplate;
|
||||
use Joomla\CMS\MVC\Model\AdminModel;
|
||||
use Joomla\Database\ParameterType;
|
||||
use PHPMailer\PHPMailer\Exception as phpMailerException;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users mail model.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class MailModel extends AdminModel
|
||||
{
|
||||
/**
|
||||
* Method to get the row form.
|
||||
*
|
||||
* @param array $data An optional array of data for the form to interrogate.
|
||||
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
|
||||
*
|
||||
* @return Form A Form object on success, false on failure
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getForm($data = [], $loadData = true)
|
||||
{
|
||||
// Get the form.
|
||||
$form = $this->loadForm('com_users.mail', 'mail', ['control' => 'jform', 'load_data' => $loadData]);
|
||||
|
||||
if (empty($form)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the data that should be injected in the form.
|
||||
*
|
||||
* @return mixed The data for the form.
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function loadFormData()
|
||||
{
|
||||
// Check the session for previously entered form data.
|
||||
$data = Factory::getApplication()->getUserState('com_users.display.mail.data', []);
|
||||
|
||||
$this->preprocessData('com_users.mail', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to preprocess the form
|
||||
*
|
||||
* @param Form $form A form object.
|
||||
* @param mixed $data The data expected for the form.
|
||||
* @param string $group The name of the plugin group to import (defaults to "content").
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception if there is an error loading the form.
|
||||
*/
|
||||
protected function preprocessForm(Form $form, $data, $group = 'user')
|
||||
{
|
||||
parent::preprocessForm($form, $data, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the email
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$data = $app->getInput()->post->get('jform', [], 'array');
|
||||
$user = $this->getCurrentUser();
|
||||
$access = new Access();
|
||||
$db = $this->getDatabase();
|
||||
$language = Factory::getLanguage();
|
||||
|
||||
$mode = \array_key_exists('mode', $data) ? (int) $data['mode'] : 0;
|
||||
$subject = \array_key_exists('subject', $data) ? $data['subject'] : '';
|
||||
$grp = \array_key_exists('group', $data) ? (int) $data['group'] : 0;
|
||||
$recurse = \array_key_exists('recurse', $data) ? (int) $data['recurse'] : 0;
|
||||
$bcc = \array_key_exists('bcc', $data) ? (int) $data['bcc'] : 0;
|
||||
$disabled = \array_key_exists('disabled', $data) ? (int) $data['disabled'] : 0;
|
||||
$message_body = \array_key_exists('message', $data) ? $data['message'] : '';
|
||||
|
||||
// Automatically removes html formatting
|
||||
if (!$mode) {
|
||||
$message_body = InputFilter::getInstance()->clean($message_body, 'string');
|
||||
}
|
||||
|
||||
// Check for a message body and subject
|
||||
if (!$message_body || !$subject) {
|
||||
$app->setUserState('com_users.display.mail.data', $data);
|
||||
$this->setError(Text::_('COM_USERS_MAIL_PLEASE_FILL_IN_THE_FORM_CORRECTLY'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get users in the group out of the ACL, if group is provided.
|
||||
$to = $grp !== 0 ? $access->getUsersByGroup($grp, $recurse) : [];
|
||||
|
||||
// When group is provided but no users are found in the group.
|
||||
if ($grp !== 0 && !$to) {
|
||||
$rows = [];
|
||||
} else {
|
||||
// Get all users email and group except for senders
|
||||
$uid = (int) $user->id;
|
||||
$query = $db->getQuery(true)
|
||||
->select(
|
||||
[
|
||||
$db->quoteName('email'),
|
||||
$db->quoteName('name'),
|
||||
]
|
||||
)
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('id') . ' != :id')
|
||||
->bind(':id', $uid, ParameterType::INTEGER);
|
||||
|
||||
if ($grp !== 0) {
|
||||
$query->whereIn($db->quoteName('id'), $to);
|
||||
}
|
||||
|
||||
if ($disabled === 0) {
|
||||
$query->where($db->quoteName('block') . ' = 0');
|
||||
}
|
||||
|
||||
$db->setQuery($query);
|
||||
$rows = $db->loadObjectList();
|
||||
}
|
||||
|
||||
// Check to see if there are any users in this group before we continue
|
||||
if (!$rows) {
|
||||
$app->setUserState('com_users.display.mail.data', $data);
|
||||
|
||||
if (\in_array($user->id, $to)) {
|
||||
$this->setError(Text::_('COM_USERS_MAIL_ONLY_YOU_COULD_BE_FOUND_IN_THIS_GROUP'));
|
||||
} else {
|
||||
$this->setError(Text::_('COM_USERS_MAIL_NO_USERS_COULD_BE_FOUND_IN_THIS_GROUP'));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the Mailer
|
||||
$mailer = new MailTemplate('com_users.massmail.mail', $language->getTag());
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
|
||||
try {
|
||||
// Build email message format.
|
||||
$data = [
|
||||
'subject' => stripslashes($subject),
|
||||
'body' => $message_body,
|
||||
'subjectprefix' => $params->get('mailSubjectPrefix', ''),
|
||||
'bodysuffix' => $params->get('mailBodySuffix', ''),
|
||||
];
|
||||
$mailer->addTemplateData($data);
|
||||
|
||||
$recipientType = $bcc ? 'bcc' : 'to';
|
||||
|
||||
// Add recipients
|
||||
foreach ($rows as $row) {
|
||||
$mailer->addRecipient($row->email, $row->name, $recipientType);
|
||||
}
|
||||
|
||||
if ($bcc) {
|
||||
$mailer->addRecipient($app->get('mailfrom'), $app->get('fromname'));
|
||||
}
|
||||
|
||||
// Send the Mail
|
||||
$rs = $mailer->send();
|
||||
} catch (MailDisabledException | phpMailerException $exception) {
|
||||
try {
|
||||
Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
|
||||
|
||||
$rs = false;
|
||||
} catch (\RuntimeException $exception) {
|
||||
Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
|
||||
|
||||
$rs = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an error
|
||||
if ($rs !== true) {
|
||||
$app->setUserState('com_users.display.mail.data', $data);
|
||||
$this->setError($mailer->ErrorInfo);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($rs)) {
|
||||
$app->setUserState('com_users.display.mail.data', $data);
|
||||
$this->setError(Text::_('COM_USERS_MAIL_THE_MAIL_COULD_NOT_BE_SENT'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the data (specially for the 'mode', 'group' and 'bcc': they could not exist in the array
|
||||
* when the box is not checked and in this case, the default value would be used instead of the '0'
|
||||
* one)
|
||||
*/
|
||||
$data['mode'] = $mode;
|
||||
$data['subject'] = $subject;
|
||||
$data['group'] = $grp;
|
||||
$data['recurse'] = $recurse;
|
||||
$data['bcc'] = $bcc;
|
||||
$data['message'] = $message_body;
|
||||
$app->setUserState('com_users.display.mail.data', []);
|
||||
$app->enqueueMessage(Text::plural('COM_USERS_MAIL_EMAIL_SENT_TO_N_USERS', \count($rows)), 'message');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
257
administrator/components/com_users/src/Model/MethodModel.php
Normal file
257
administrator/components/com_users/src/Model/MethodModel.php
Normal file
@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Event\MultiFactor\GetSetup;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\DataShape\SetupRenderOptions;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Table\MfaTable;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Multi-factor Authentication management model
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class MethodModel extends BaseDatabaseModel
|
||||
{
|
||||
/**
|
||||
* List of MFA Methods
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $mfaMethods = null;
|
||||
|
||||
/**
|
||||
* Get the specified MFA Method's record
|
||||
*
|
||||
* @param string $method The Method to retrieve.
|
||||
*
|
||||
* @return array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getMethod(string $method): array
|
||||
{
|
||||
if (!$this->methodExists($method)) {
|
||||
return [
|
||||
'name' => $method,
|
||||
'display' => '',
|
||||
'shortinfo' => '',
|
||||
'image' => '',
|
||||
'canDisable' => true,
|
||||
'allowMultiple' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->mfaMethods[$method];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the specified MFA Method available?
|
||||
*
|
||||
* @param string $method The Method to check.
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function methodExists(string $method): bool
|
||||
{
|
||||
if (!\is_array($this->mfaMethods)) {
|
||||
$this->populateMfaMethods();
|
||||
}
|
||||
|
||||
return isset($this->mfaMethods[$method]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|null $user The user record. Null to use the currently logged in user.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getRenderOptions(?User $user = null): SetupRenderOptions
|
||||
{
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
$renderOptions = new SetupRenderOptions();
|
||||
|
||||
$event = new GetSetup($this->getRecord($user));
|
||||
$results = Factory::getApplication()
|
||||
->getDispatcher()
|
||||
->dispatch($event->getName(), $event)
|
||||
->getArgument('result', []);
|
||||
|
||||
if (empty($results)) {
|
||||
return $renderOptions;
|
||||
}
|
||||
|
||||
foreach ($results as $result) {
|
||||
if (empty($result)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $renderOptions->merge($result);
|
||||
}
|
||||
|
||||
return $renderOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specified MFA record. It will return a fake default record when no record ID is specified.
|
||||
*
|
||||
* @param User|null $user The user record. Null to use the currently logged in user.
|
||||
*
|
||||
* @return MfaTable
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getRecord(User $user = null): MfaTable
|
||||
{
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
$defaultRecord = $this->getDefaultRecord($user);
|
||||
$id = (int) $this->getState('id', 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
return $defaultRecord;
|
||||
}
|
||||
|
||||
/** @var MfaTable $record */
|
||||
$record = $this->getTable('Mfa', 'Administrator');
|
||||
$loaded = $record->load(
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'id' => $id,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$loaded) {
|
||||
return $defaultRecord;
|
||||
}
|
||||
|
||||
if (!$this->methodExists($record->method)) {
|
||||
return $defaultRecord;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title to use for the page
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getPageTitle(): string
|
||||
{
|
||||
$task = $this->getState('task', 'edit');
|
||||
|
||||
switch ($task) {
|
||||
case 'mfa':
|
||||
$key = 'COM_USERS_USER_MULTIFACTOR_AUTH';
|
||||
break;
|
||||
|
||||
default:
|
||||
$key = sprintf('COM_USERS_MFA_%s_PAGE_HEAD', $task);
|
||||
break;
|
||||
}
|
||||
|
||||
return Text::_($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|null $user The user record. Null to use the current user.
|
||||
*
|
||||
* @return MfaTable
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected function getDefaultRecord(?User $user = null): MfaTable
|
||||
{
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
$method = $this->getState('method');
|
||||
$title = '';
|
||||
|
||||
if (\is_null($this->mfaMethods)) {
|
||||
$this->populateMfaMethods();
|
||||
}
|
||||
|
||||
if ($method && isset($this->mfaMethods[$method])) {
|
||||
$title = $this->mfaMethods[$method]['display'];
|
||||
}
|
||||
|
||||
/** @var MfaTable $record */
|
||||
$record = $this->getTable('Mfa', 'Administrator');
|
||||
|
||||
$record->bind(
|
||||
[
|
||||
'id' => null,
|
||||
'user_id' => $user->id,
|
||||
'title' => $title,
|
||||
'method' => $method,
|
||||
'default' => 0,
|
||||
'options' => [],
|
||||
]
|
||||
);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the list of MFA Methods
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function populateMfaMethods(): void
|
||||
{
|
||||
$this->mfaMethods = [];
|
||||
$mfaMethods = MfaHelper::getMfaMethods();
|
||||
|
||||
if (empty($mfaMethods)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($mfaMethods as $method) {
|
||||
$this->mfaMethods[$method['name']] = $method;
|
||||
}
|
||||
|
||||
// We also need to add the backup codes Method
|
||||
$this->mfaMethods['backupcodes'] = [
|
||||
'name' => 'backupcodes',
|
||||
'display' => Text::_('COM_USERS_USER_BACKUPCODES'),
|
||||
'shortinfo' => Text::_('COM_USERS_USER_BACKUPCODES_DESC'),
|
||||
'image' => 'media/com_users/images/emergency.svg',
|
||||
'canDisable' => false,
|
||||
'allowMultiple' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
218
administrator/components/com_users/src/Model/MethodsModel.php
Normal file
218
administrator/components/com_users/src/Model/MethodsModel.php
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Multi-factor Authentication Methods list page's model
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class MethodsModel extends BaseDatabaseModel
|
||||
{
|
||||
/**
|
||||
* Returns a list of all available MFA methods and their currently active records for a given user.
|
||||
*
|
||||
* @param User|null $user The user object. Skip to use the current user.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getMethods(?User $user = null): array
|
||||
{
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
if ($user->guest) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get an associative array of MFA Methods
|
||||
$rawMethods = MfaHelper::getMfaMethods();
|
||||
$methods = [];
|
||||
|
||||
foreach ($rawMethods as $method) {
|
||||
$method['active'] = [];
|
||||
$methods[$method['name']] = $method;
|
||||
}
|
||||
|
||||
// Put the user MFA records into the Methods array
|
||||
$userMfaRecords = MfaHelper::getUserMfaRecords($user->id);
|
||||
|
||||
if (!empty($userMfaRecords)) {
|
||||
foreach ($userMfaRecords as $record) {
|
||||
if (!isset($methods[$record->method])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$methods[$record->method]->addActiveMethod($record);
|
||||
}
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all Multi-factor Authentication Methods for the given user.
|
||||
*
|
||||
* @param User|null $user The user object to reset MFA for. Null to use the current user.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function deleteAll(?User $user = null): void
|
||||
{
|
||||
// Make sure we have a user object
|
||||
if (\is_null($user)) {
|
||||
$user = $this->getCurrentUser() ?: Factory::getApplication()->getIdentity();
|
||||
}
|
||||
|
||||
// If the user object is a guest (who can't have MFA) we stop with an error
|
||||
if ($user->guest) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->delete($db->quoteName('#__user_mfa'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->bind(':user_id', $user->id, ParameterType::INTEGER);
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a relative timestamp. It deals with timestamps today and yesterday in a special manner. Example returns:
|
||||
* Yesterday, 13:12
|
||||
* Today, 08:33
|
||||
* January 1, 2015
|
||||
*
|
||||
* @param string $dateTimeText The database time string to use, e.g. "2017-01-13 13:25:36"
|
||||
*
|
||||
* @return string The formatted, human-readable date
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function formatRelative(?string $dateTimeText): string
|
||||
{
|
||||
if (empty($dateTimeText)) {
|
||||
return Text::_('JNEVER');
|
||||
}
|
||||
|
||||
// The timestamp is given in UTC. Make sure Joomla! parses it as such.
|
||||
$utcTimeZone = new \DateTimeZone('UTC');
|
||||
$jDate = new Date($dateTimeText, $utcTimeZone);
|
||||
$unixStamp = $jDate->toUnix();
|
||||
|
||||
// I'm pretty sure we didn't have MFA in Joomla back in 1970 ;)
|
||||
if ($unixStamp < 0) {
|
||||
return Text::_('JNEVER');
|
||||
}
|
||||
|
||||
// I need to display the date in the user's local timezone. That's how you do it.
|
||||
$user = $this->getCurrentUser();
|
||||
$userTZ = $user->getParam('timezone', 'UTC');
|
||||
$tz = new \DateTimeZone($userTZ);
|
||||
$jDate->setTimezone($tz);
|
||||
|
||||
// Default format string: way in the past, the time of the day is not important
|
||||
$formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_PAST');
|
||||
$containerString = Text::_('COM_USERS_MFA_LBL_PAST');
|
||||
|
||||
// If the timestamp is within the last 72 hours we may need a special format
|
||||
if ($unixStamp > (time() - (72 * 3600))) {
|
||||
// Is this timestamp today?
|
||||
$jNow = new Date();
|
||||
$jNow->setTimezone($tz);
|
||||
$checkNow = $jNow->format('Ymd', true);
|
||||
$checkDate = $jDate->format('Ymd', true);
|
||||
|
||||
if ($checkDate == $checkNow) {
|
||||
$formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_TODAY');
|
||||
$containerString = Text::_('COM_USERS_MFA_LBL_TODAY');
|
||||
} else {
|
||||
// Is this timestamp yesterday?
|
||||
$jYesterday = clone $jNow;
|
||||
$jYesterday->setTime(0, 0, 0);
|
||||
$oneSecond = new \DateInterval('PT1S');
|
||||
$jYesterday->sub($oneSecond);
|
||||
$checkYesterday = $jYesterday->format('Ymd', true);
|
||||
|
||||
if ($checkDate == $checkYesterday) {
|
||||
$formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_YESTERDAY');
|
||||
$containerString = Text::_('COM_USERS_MFA_LBL_YESTERDAY');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf($containerString, $jDate->format($formatString, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user's "don't show this again" flag.
|
||||
*
|
||||
* @param User $user The user to check
|
||||
* @param bool $flag True to set the flag, false to unset it (it will be set to 0, actually)
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function setFlag(User $user, bool $flag = true): void
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$profileKey = 'mfa.dontshow';
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('profile_value'))
|
||||
->from($db->quoteName('#__user_profiles'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->where($db->quoteName('profile_key') . ' = :profileKey')
|
||||
->bind(':user_id', $user->id, ParameterType::INTEGER)
|
||||
->bind(':profileKey', $profileKey, ParameterType::STRING);
|
||||
|
||||
try {
|
||||
$result = $db->setQuery($query)->loadResult();
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = !\is_null($result);
|
||||
|
||||
$object = (object) [
|
||||
'user_id' => $user->id,
|
||||
'profile_key' => 'mfa.dontshow',
|
||||
'profile_value' => ($flag ? 1 : 0),
|
||||
'ordering' => 1,
|
||||
];
|
||||
|
||||
if (!$exists) {
|
||||
$db->insertObject('#__user_profiles', $object);
|
||||
} else {
|
||||
$db->updateObject('#__user_profiles', $object, ['user_id', 'profile_key']);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
administrator/components/com_users/src/Model/NoteModel.php
Normal file
140
administrator/components/com_users/src/Model/NoteModel.php
Normal file
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Model\AdminModel;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Versioning\VersionableModelTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User note model.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class NoteModel extends AdminModel
|
||||
{
|
||||
use VersionableModelTrait;
|
||||
|
||||
/**
|
||||
* The type alias for this content type.
|
||||
*
|
||||
* @var string
|
||||
* @since 3.2
|
||||
*/
|
||||
public $typeAlias = 'com_users.note';
|
||||
|
||||
/**
|
||||
* Method to get the record form.
|
||||
*
|
||||
* @param array $data Data for the form.
|
||||
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
|
||||
*
|
||||
* @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function getForm($data = [], $loadData = true)
|
||||
{
|
||||
// Get the form.
|
||||
$form = $this->loadForm('com_users.note', 'note', ['control' => 'jform', 'load_data' => $loadData]);
|
||||
|
||||
if (empty($form)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a single record.
|
||||
*
|
||||
* @param integer $pk The id of the primary key.
|
||||
*
|
||||
* @return mixed Object on success, false on failure.
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getItem($pk = null)
|
||||
{
|
||||
$result = parent::getItem($pk);
|
||||
|
||||
// Get the dispatcher and load the content plugins.
|
||||
PluginHelper::importPlugin('content');
|
||||
|
||||
// Load the user plugins for backward compatibility (v3.3.3 and earlier).
|
||||
PluginHelper::importPlugin('user');
|
||||
|
||||
// Trigger the data preparation event.
|
||||
Factory::getApplication()->triggerEvent('onContentPrepareData', ['com_users.note', $result]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the data that should be injected in the form.
|
||||
*
|
||||
* @return mixed The data for the form.
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function loadFormData()
|
||||
{
|
||||
// Get the application
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Check the session for previously entered form data.
|
||||
$data = $app->getUserState('com_users.edit.note.data', []);
|
||||
|
||||
if (empty($data)) {
|
||||
$data = $this->getItem();
|
||||
|
||||
// Prime some default values.
|
||||
if ($this->getState('note.id') == 0) {
|
||||
$data->set('catid', $app->getInput()->get('catid', $app->getUserState('com_users.notes.filter.category_id'), 'int'));
|
||||
}
|
||||
|
||||
$userId = $app->getInput()->get('u_id', 0, 'int');
|
||||
|
||||
if ($userId != 0) {
|
||||
$data->user_id = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
$this->preprocessData('com_users.note', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function populateState()
|
||||
{
|
||||
parent::populateState();
|
||||
|
||||
$userId = Factory::getApplication()->getInput()->get('u_id', 0, 'int');
|
||||
$this->setState('note.user_id', $userId);
|
||||
}
|
||||
}
|
||||
231
administrator/components/com_users/src/Model/NotesModel.php
Normal file
231
administrator/components/com_users/src/Model/NotesModel.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Database\DatabaseQuery;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User notes model class.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class NotesModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
// Set the list ordering fields.
|
||||
if (empty($config['filter_fields'])) {
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'user_id', 'a.user_id',
|
||||
'u.name',
|
||||
'subject', 'a.subject',
|
||||
'catid', 'a.catid', 'category_id',
|
||||
'state', 'a.state', 'published',
|
||||
'c.title',
|
||||
'review_time', 'a.review_time',
|
||||
'publish_up', 'a.publish_up',
|
||||
'publish_down', 'a.publish_down',
|
||||
'level', 'c.level',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return DatabaseQuery A DatabaseQuery object to retrieve the data set.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
protected function getListQuery()
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Select the required fields from the table.
|
||||
$query->select(
|
||||
$this->getState(
|
||||
'list.select',
|
||||
'a.id, a.subject, a.checked_out, a.checked_out_time,' .
|
||||
'a.catid, a.created_time, a.review_time,' .
|
||||
'a.state, a.publish_up, a.publish_down'
|
||||
)
|
||||
);
|
||||
$query->from('#__user_notes AS a');
|
||||
|
||||
// Join over the category
|
||||
$query->select('c.title AS category_title, c.params AS category_params')
|
||||
->join('LEFT', '#__categories AS c ON c.id = a.catid');
|
||||
|
||||
// Join over the users for the note user.
|
||||
$query->select('u.name AS user_name')
|
||||
->join('LEFT', '#__users AS u ON u.id = a.user_id');
|
||||
|
||||
// Join over the users for the checked out user.
|
||||
$query->select('uc.name AS editor')
|
||||
->join('LEFT', '#__users AS uc ON uc.id = a.checked_out');
|
||||
|
||||
// Filter by search in title
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search)) {
|
||||
if (stripos($search, 'id:') === 0) {
|
||||
$search3 = (int) substr($search, 3);
|
||||
$query->where($db->quoteName('a.id') . ' = :id');
|
||||
$query->bind(':id', $search3, ParameterType::INTEGER);
|
||||
} elseif (stripos($search, 'uid:') === 0) {
|
||||
$search4 = (int) substr($search, 4);
|
||||
$query->where($db->quoteName('a.user_id') . ' = :id');
|
||||
$query->bind(':id', $search4, ParameterType::INTEGER);
|
||||
} else {
|
||||
$search = '%' . trim($search) . '%';
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.subject') . ' LIKE :subject'
|
||||
. ' OR ' . $db->quoteName('u.name') . ' LIKE :name'
|
||||
. ' OR ' . $db->quoteName('u.username') . ' LIKE :username)'
|
||||
);
|
||||
$query->bind(':subject', $search);
|
||||
$query->bind(':name', $search);
|
||||
$query->bind(':username', $search);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by published state
|
||||
$published = $this->getState('filter.published');
|
||||
|
||||
if (is_numeric($published)) {
|
||||
$query->where($db->quoteName('a.state') . ' = :state')
|
||||
->bind(':state', $published, ParameterType::INTEGER);
|
||||
} elseif ($published !== '*') {
|
||||
$query->whereIn($db->quoteName('a.state'), [0, 1]);
|
||||
}
|
||||
|
||||
// Filter by a single category.
|
||||
$categoryId = (int) $this->getState('filter.category_id');
|
||||
|
||||
if ($categoryId) {
|
||||
$query->where($db->quoteName('a.catid') . ' = :catid')
|
||||
->bind(':catid', $categoryId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
// Filter by a single user.
|
||||
$userId = (int) $this->getState('filter.user_id');
|
||||
|
||||
if ($userId) {
|
||||
// Add the body and where filter.
|
||||
$query->select('a.body')
|
||||
->where($db->quoteName('a.user_id') . ' = :user_id')
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
// Filter on the level.
|
||||
if ($level = $this->getState('filter.level')) {
|
||||
$level = (int) $level;
|
||||
$query->where($db->quoteName('c.level') . ' <= :level')
|
||||
->bind(':level', $level, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
// Add the list ordering clause.
|
||||
$query->order($db->escape($this->getState('list.ordering', 'a.review_time')) . ' ' . $db->escape($this->getState('list.direction', 'DESC')));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a store id based on model configuration state.
|
||||
*
|
||||
* This is necessary because the model is used by the component and
|
||||
* different modules that might need different sets of data or different
|
||||
* ordering requirements.
|
||||
*
|
||||
* @param string $id A prefix for the store id.
|
||||
*
|
||||
* @return string A store id.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
protected function getStoreId($id = '')
|
||||
{
|
||||
// Compile the store id.
|
||||
$id .= ':' . $this->getState('filter.search');
|
||||
$id .= ':' . $this->getState('filter.published');
|
||||
$id .= ':' . $this->getState('filter.category_id');
|
||||
$id .= ':' . $this->getState('filter.user_id');
|
||||
$id .= ':' . $this->getState('filter.level');
|
||||
|
||||
return parent::getStoreId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user object if the user filter is set.
|
||||
*
|
||||
* @return User The User object
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
// Filter by search in title
|
||||
$search = (int) $this->getState('filter.user_id');
|
||||
|
||||
if ($search != 0) {
|
||||
$user->load((int) $search);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @param string $ordering An optional ordering field.
|
||||
* @param string $direction An optional direction (asc|desc).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function populateState($ordering = 'a.review_time', $direction = 'desc')
|
||||
{
|
||||
// Adjust the context to support modal layouts.
|
||||
if ($layout = Factory::getApplication()->getInput()->get('layout')) {
|
||||
$this->context .= '.' . $layout;
|
||||
}
|
||||
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
}
|
||||
1079
administrator/components/com_users/src/Model/UserModel.php
Normal file
1079
administrator/components/com_users/src/Model/UserModel.php
Normal file
File diff suppressed because it is too large
Load Diff
607
administrator/components/com_users/src/Model/UsersModel.php
Normal file
607
administrator/components/com_users/src/Model/UsersModel.php
Normal file
@ -0,0 +1,607 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Model;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Form\Form;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Database\DatabaseQuery;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Methods supporting a list of user records.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class UsersModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* A list of filter variables to not merge into the model's state
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $filterForbiddenList = ['groups', 'excluded'];
|
||||
|
||||
/**
|
||||
* Override parent constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param MVCFactoryInterface $factory The factory.
|
||||
*
|
||||
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
* @since 3.2
|
||||
*/
|
||||
public function __construct($config = [], MVCFactoryInterface $factory = null)
|
||||
{
|
||||
if (empty($config['filter_fields'])) {
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'name', 'a.name',
|
||||
'username', 'a.username',
|
||||
'email', 'a.email',
|
||||
'block', 'a.block',
|
||||
'sendEmail', 'a.sendEmail',
|
||||
'registerDate', 'a.registerDate',
|
||||
'lastvisitDate', 'a.lastvisitDate',
|
||||
'activation', 'a.activation',
|
||||
'active',
|
||||
'group_id',
|
||||
'range',
|
||||
'lastvisitrange',
|
||||
'state',
|
||||
'mfa',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to auto-populate the model state.
|
||||
*
|
||||
* Note. Calling getState in this method will result in recursion.
|
||||
*
|
||||
* @param string $ordering An optional ordering field.
|
||||
* @param string $direction An optional direction (asc|desc).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function populateState($ordering = 'a.name', $direction = 'asc')
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$input = $app->getInput();
|
||||
|
||||
// Adjust the context to support modal layouts.
|
||||
if ($layout = $input->get('layout', 'default', 'cmd')) {
|
||||
$this->context .= '.' . $layout;
|
||||
}
|
||||
|
||||
$groups = json_decode(base64_decode($input->get('groups', '', 'BASE64')));
|
||||
|
||||
if (isset($groups)) {
|
||||
$groups = ArrayHelper::toInteger($groups);
|
||||
}
|
||||
|
||||
$this->setState('filter.groups', $groups);
|
||||
|
||||
$excluded = json_decode(base64_decode($input->get('excluded', '', 'BASE64')));
|
||||
|
||||
if (isset($excluded)) {
|
||||
$excluded = ArrayHelper::toInteger($excluded);
|
||||
}
|
||||
|
||||
$this->setState('filter.excluded', $excluded);
|
||||
|
||||
// Load the parameters.
|
||||
$params = ComponentHelper::getParams('com_users');
|
||||
$this->setState('params', $params);
|
||||
|
||||
// List state information.
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a store id based on model configuration state.
|
||||
*
|
||||
* This is necessary because the model is used by the component and
|
||||
* different modules that might need different sets of data or different
|
||||
* ordering requirements.
|
||||
*
|
||||
* @param string $id A prefix for the store id.
|
||||
*
|
||||
* @return string A store id.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function getStoreId($id = '')
|
||||
{
|
||||
// Compile the store id.
|
||||
$id .= ':' . $this->getState('filter.search');
|
||||
$id .= ':' . $this->getState('filter.active');
|
||||
$id .= ':' . $this->getState('filter.state');
|
||||
$id .= ':' . $this->getState('filter.group_id');
|
||||
$id .= ':' . $this->getState('filter.range');
|
||||
|
||||
if (PluginHelper::isEnabled('multifactorauth')) {
|
||||
$id .= ':' . $this->getState('filter.mfa');
|
||||
}
|
||||
|
||||
return parent::getStoreId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of users and adds expensive joins to the result set.
|
||||
*
|
||||
* @return mixed An array of data items on success, false on failure.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function getItems()
|
||||
{
|
||||
// Get a storage key.
|
||||
$store = $this->getStoreId();
|
||||
|
||||
// Try to load the data from internal storage.
|
||||
if (empty($this->cache[$store])) {
|
||||
$groups = $this->getState('filter.groups');
|
||||
$groupId = $this->getState('filter.group_id');
|
||||
|
||||
if (isset($groups) && (empty($groups) || $groupId && !\in_array($groupId, $groups))) {
|
||||
$items = [];
|
||||
} else {
|
||||
$items = parent::getItems();
|
||||
}
|
||||
|
||||
// Bail out on an error or empty list.
|
||||
if (empty($items)) {
|
||||
$this->cache[$store] = $items;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
// Joining the groups with the main query is a performance hog.
|
||||
// Find the information only on the result set.
|
||||
|
||||
// First pass: get list of the user ids and reset the counts.
|
||||
$userIds = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$userIds[] = (int) $item->id;
|
||||
|
||||
$item->group_count = 0;
|
||||
$item->group_names = '';
|
||||
$item->note_count = 0;
|
||||
}
|
||||
|
||||
// Get the counts from the database only for the users in the list.
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Join over the group mapping table.
|
||||
$query->select('map.user_id, COUNT(map.group_id) AS group_count')
|
||||
->from('#__user_usergroup_map AS map')
|
||||
->whereIn($db->quoteName('map.user_id'), $userIds)
|
||||
->group('map.user_id')
|
||||
// Join over the user groups table.
|
||||
->join('LEFT', '#__usergroups AS g2 ON g2.id = map.group_id');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
// Load the counts into an array indexed on the user id field.
|
||||
try {
|
||||
$userGroups = $db->loadObjectList('user_id');
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$query->clear()
|
||||
->select('n.user_id, COUNT(n.id) As note_count')
|
||||
->from('#__user_notes AS n')
|
||||
->whereIn($db->quoteName('n.user_id'), $userIds)
|
||||
->where('n.state >= 0')
|
||||
->group('n.user_id');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
// Load the counts into an array indexed on the aro.value field (the user id).
|
||||
try {
|
||||
$userNotes = $db->loadObjectList('user_id');
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Second pass: collect the group counts into the main items array.
|
||||
foreach ($items as &$item) {
|
||||
if (isset($userGroups[$item->id])) {
|
||||
$item->group_count = $userGroups[$item->id]->group_count;
|
||||
|
||||
// Group_concat in other databases is not supported
|
||||
$item->group_names = $this->getUserDisplayedGroups($item->id);
|
||||
}
|
||||
|
||||
if (isset($userNotes[$item->id])) {
|
||||
$item->note_count = $userNotes[$item->id]->note_count;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the items to the internal cache.
|
||||
$this->cache[$store] = $items;
|
||||
}
|
||||
|
||||
return $this->cache[$store];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter form
|
||||
*
|
||||
* @param array $data data
|
||||
* @param boolean $loadData load current data
|
||||
*
|
||||
* @return Form|null The \JForm object or null if the form can't be found
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function getFilterForm($data = [], $loadData = true)
|
||||
{
|
||||
$form = parent::getFilterForm($data, $loadData);
|
||||
|
||||
if ($form && !PluginHelper::isEnabled('multifactorauth')) {
|
||||
$form->removeField('mfa', 'filter');
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return DatabaseQuery
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function getListQuery()
|
||||
{
|
||||
// Create a new query object.
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
// Select the required fields from the table.
|
||||
$query->select(
|
||||
$this->getState(
|
||||
'list.select',
|
||||
'a.*'
|
||||
)
|
||||
);
|
||||
|
||||
$query->from($db->quoteName('#__users') . ' AS a');
|
||||
|
||||
// Include MFA information
|
||||
if (PluginHelper::isEnabled('multifactorauth')) {
|
||||
$subQuery = $db->getQuery(true)
|
||||
->select(
|
||||
[
|
||||
'MIN(' . $db->quoteName('user_id') . ') AS ' . $db->quoteName('uid'),
|
||||
'COUNT(*) AS ' . $db->quoteName('mfaRecords'),
|
||||
]
|
||||
)
|
||||
->from($db->quoteName('#__user_mfa'))
|
||||
->group($db->quoteName('user_id'));
|
||||
$query->select($db->quoteName('mfa.mfaRecords'))
|
||||
->join(
|
||||
'left',
|
||||
'(' . $subQuery . ') AS ' . $db->quoteName('mfa'),
|
||||
$db->quoteName('mfa.uid') . ' = ' . $db->quoteName('a.id')
|
||||
);
|
||||
|
||||
$mfaState = $this->getState('filter.mfa');
|
||||
|
||||
if (is_numeric($mfaState)) {
|
||||
$mfaState = (int) $mfaState;
|
||||
|
||||
if ($mfaState === 1) {
|
||||
$query->where(
|
||||
'((' . $db->quoteName('mfa.mfaRecords') . ' > 0) OR (' .
|
||||
$db->quoteName('a.otpKey') . ' IS NOT NULL AND ' .
|
||||
$db->quoteName('a.otpKey') . ' != ' . $db->quote('') . '))'
|
||||
);
|
||||
} else {
|
||||
$query->where(
|
||||
'((' . $db->quoteName('mfa.mfaRecords') . ' = 0 OR ' .
|
||||
$db->quoteName('mfa.mfaRecords') . ' IS NULL) AND (' .
|
||||
$db->quoteName('a.otpKey') . ' IS NULL OR ' .
|
||||
$db->quoteName('a.otpKey') . ' = ' . $db->quote('') . '))'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the model is set to check item state, add to the query.
|
||||
$state = $this->getState('filter.state');
|
||||
|
||||
if (is_numeric($state)) {
|
||||
$query->where($db->quoteName('a.block') . ' = :state')
|
||||
->bind(':state', $state, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
// If the model is set to check the activated state, add to the query.
|
||||
$active = $this->getState('filter.active');
|
||||
|
||||
if (is_numeric($active)) {
|
||||
if ($active == '0') {
|
||||
$query->whereIn($db->quoteName('a.activation'), ['', '0']);
|
||||
} elseif ($active == '1') {
|
||||
$query->where($query->length($db->quoteName('a.activation')) . ' > 1');
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the items over the group id if set.
|
||||
$groupId = $this->getState('filter.group_id');
|
||||
$groups = $this->getState('filter.groups');
|
||||
|
||||
if ($groupId || isset($groups)) {
|
||||
$group_by = [
|
||||
'a.id',
|
||||
'a.name',
|
||||
'a.username',
|
||||
'a.password',
|
||||
'a.block',
|
||||
'a.sendEmail',
|
||||
'a.registerDate',
|
||||
'a.lastvisitDate',
|
||||
'a.activation',
|
||||
'a.params',
|
||||
'a.email',
|
||||
'a.lastResetTime',
|
||||
'a.resetCount',
|
||||
'a.otpKey',
|
||||
'a.otep',
|
||||
'a.requireReset',
|
||||
];
|
||||
|
||||
if (PluginHelper::isEnabled('multifactorauth')) {
|
||||
$group_by[] = 'mfa.mfaRecords';
|
||||
}
|
||||
|
||||
$query->join('LEFT', '#__user_usergroup_map AS map2 ON map2.user_id = a.id')
|
||||
->group($db->quoteName($group_by));
|
||||
|
||||
if ($groupId) {
|
||||
$groupId = (int) $groupId;
|
||||
$query->where($db->quoteName('map2.group_id') . ' = :group_id')
|
||||
->bind(':group_id', $groupId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
if (isset($groups)) {
|
||||
$query->whereIn($db->quoteName('map2.group_id'), $groups);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the items over the search string if set.
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search)) {
|
||||
if (stripos($search, 'id:') === 0) {
|
||||
$ids = (int) substr($search, 3);
|
||||
$query->where($db->quoteName('a.id') . ' = :id');
|
||||
$query->bind(':id', $ids, ParameterType::INTEGER);
|
||||
} elseif (stripos($search, 'username:') === 0) {
|
||||
$search = '%' . substr($search, 9) . '%';
|
||||
$query->where($db->quoteName('a.username') . ' LIKE :username');
|
||||
$query->bind(':username', $search);
|
||||
} else {
|
||||
$search = '%' . trim($search) . '%';
|
||||
|
||||
// Add the clauses to the query.
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.name') . ' LIKE :name'
|
||||
. ' OR ' . $db->quoteName('a.username') . ' LIKE :username'
|
||||
. ' OR ' . $db->quoteName('a.email') . ' LIKE :email)'
|
||||
)
|
||||
->bind(':name', $search)
|
||||
->bind(':username', $search)
|
||||
->bind(':email', $search);
|
||||
}
|
||||
}
|
||||
|
||||
// Add filter for registration time ranges select list. UI Visitors get a range of predefined
|
||||
// values. API users can do a full range based on ISO8601
|
||||
$range = $this->getState('filter.range');
|
||||
$registrationStart = $this->getState('filter.registrationDateStart');
|
||||
$registrationEnd = $this->getState('filter.registrationDateEnd');
|
||||
|
||||
// Apply the range filter.
|
||||
if ($range || ($registrationStart && $registrationEnd)) {
|
||||
if ($range) {
|
||||
$dates = $this->buildDateRange($range);
|
||||
} else {
|
||||
$dates = [
|
||||
'dNow' => $registrationEnd,
|
||||
'dStart' => $registrationStart,
|
||||
];
|
||||
}
|
||||
|
||||
if ($dates['dStart'] !== false) {
|
||||
$dStart = $dates['dStart']->format('Y-m-d H:i:s');
|
||||
|
||||
if ($dates['dNow'] === false) {
|
||||
$query->where($db->quoteName('a.registerDate') . ' < :registerDate');
|
||||
$query->bind(':registerDate', $dStart);
|
||||
} else {
|
||||
$dNow = $dates['dNow']->format('Y-m-d H:i:s');
|
||||
|
||||
$query->where($db->quoteName('a.registerDate') . ' BETWEEN :registerDate1 AND :registerDate2');
|
||||
$query->bind(':registerDate1', $dStart);
|
||||
$query->bind(':registerDate2', $dNow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add filter for last visit time ranges select list. UI Visitors get a range of predefined
|
||||
// values. API users can do a full range based on ISO8601
|
||||
$lastvisitrange = $this->getState('filter.lastvisitrange');
|
||||
$lastVisitStart = $this->getState('filter.lastVisitStart');
|
||||
$lastVisitEnd = $this->getState('filter.lastVisitEnd');
|
||||
|
||||
// Apply the range filter.
|
||||
if ($lastvisitrange || ($lastVisitStart && $lastVisitEnd)) {
|
||||
if ($lastvisitrange) {
|
||||
$dates = $this->buildDateRange($lastvisitrange);
|
||||
} else {
|
||||
$dates = [
|
||||
'dNow' => $lastVisitEnd,
|
||||
'dStart' => $lastVisitStart,
|
||||
];
|
||||
}
|
||||
|
||||
if ($dates['dStart'] === false) {
|
||||
$query->where($db->quoteName('a.lastvisitDate') . ' IS NULL');
|
||||
} else {
|
||||
$query->where($db->quoteName('a.lastvisitDate') . ' IS NOT NULL');
|
||||
|
||||
$dStart = $dates['dStart']->format('Y-m-d H:i:s');
|
||||
|
||||
if ($dates['dNow'] === false) {
|
||||
$query->where($db->quoteName('a.lastvisitDate') . ' < :lastvisitDate');
|
||||
$query->bind(':lastvisitDate', $dStart);
|
||||
} else {
|
||||
$dNow = $dates['dNow']->format('Y-m-d H:i:s');
|
||||
|
||||
$query->where($db->quoteName('a.lastvisitDate') . ' BETWEEN :lastvisitDate1 AND :lastvisitDate2');
|
||||
$query->bind(':lastvisitDate1', $dStart);
|
||||
$query->bind(':lastvisitDate2', $dNow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by excluded users
|
||||
$excluded = $this->getState('filter.excluded');
|
||||
|
||||
if (!empty($excluded)) {
|
||||
$query->whereNotIn($db->quoteName('id'), $excluded);
|
||||
}
|
||||
|
||||
// Add the list ordering clause.
|
||||
$query->order(
|
||||
$db->quoteName($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the date range to filter on.
|
||||
*
|
||||
* @param string $range The textual range to construct the filter for.
|
||||
*
|
||||
* @return array The date range to filter on.
|
||||
*
|
||||
* @since 3.6.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function buildDateRange($range)
|
||||
{
|
||||
// Get UTC for now.
|
||||
$dNow = new Date();
|
||||
$dStart = clone $dNow;
|
||||
|
||||
switch ($range) {
|
||||
case 'past_week':
|
||||
$dStart->modify('-7 day');
|
||||
break;
|
||||
|
||||
case 'past_1month':
|
||||
$dStart->modify('-1 month');
|
||||
break;
|
||||
|
||||
case 'past_3month':
|
||||
$dStart->modify('-3 month');
|
||||
break;
|
||||
|
||||
case 'past_6month':
|
||||
$dStart->modify('-6 month');
|
||||
$arr = [];
|
||||
break;
|
||||
|
||||
case 'post_year':
|
||||
$dNow = false;
|
||||
|
||||
// No break
|
||||
|
||||
case 'past_year':
|
||||
$dStart->modify('-1 year');
|
||||
break;
|
||||
|
||||
case 'today':
|
||||
// Ranges that need to align with local 'days' need special treatment.
|
||||
$app = Factory::getApplication();
|
||||
$offset = $app->get('offset');
|
||||
|
||||
// Reset the start time to be the beginning of today, local time.
|
||||
$dStart = new Date('now', $offset);
|
||||
$dStart->setTime(0, 0, 0);
|
||||
|
||||
// Now change the timezone back to UTC.
|
||||
$tz = new \DateTimeZone('GMT');
|
||||
$dStart->setTimezone($tz);
|
||||
break;
|
||||
case 'never':
|
||||
$dNow = false;
|
||||
$dStart = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return ['dNow' => $dNow, 'dStart' => $dStart];
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL server change
|
||||
*
|
||||
* @param integer $userId User identifier
|
||||
*
|
||||
* @return string Groups titles imploded :$
|
||||
*/
|
||||
protected function getUserDisplayedGroups($userId)
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('title'))
|
||||
->from($db->quoteName('#__usergroups', 'ug'))
|
||||
->join('LEFT', $db->quoteName('#__user_usergroup_map', 'map') . ' ON (ug.id = map.group_id)')
|
||||
->where($db->quoteName('map.user_id') . ' = :user_id')
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER);
|
||||
|
||||
try {
|
||||
$result = $db->setQuery($query)->loadColumn();
|
||||
} catch (\RuntimeException $e) {
|
||||
$result = [];
|
||||
}
|
||||
|
||||
return implode("\n", $result);
|
||||
}
|
||||
}
|
||||
132
administrator/components/com_users/src/Service/Encrypt.php
Normal file
132
administrator/components/com_users/src/Service/Encrypt.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Service;
|
||||
|
||||
use Joomla\CMS\Encrypt\Aes;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Data encryption service.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class Encrypt
|
||||
{
|
||||
/**
|
||||
* The encryption engine used by this service
|
||||
*
|
||||
* @var Aes
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $aes;
|
||||
|
||||
/**
|
||||
* EncryptService constructor.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the plaintext $data and return the ciphertext prefixed by ###AES128###
|
||||
*
|
||||
* @param string $data The plaintext data
|
||||
*
|
||||
* @return string The ciphertext, prefixed by ###AES128###
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function encrypt(string $data): string
|
||||
{
|
||||
if (!\is_object($this->aes)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->aes->setPassword($this->getPassword(), false);
|
||||
$encrypted = $this->aes->encryptString($data, true);
|
||||
|
||||
return '###AES128###' . $encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the ciphertext, prefixed by ###AES128###, and return the plaintext.
|
||||
*
|
||||
* @param string $data The ciphertext, prefixed by ###AES128###
|
||||
* @param bool $legacy Use legacy key expansion. We recommend against using it.
|
||||
*
|
||||
* @return string The plaintext data
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function decrypt(string $data, bool $legacy = false): string
|
||||
{
|
||||
if (substr($data, 0, 12) != '###AES128###') {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data = substr($data, 12);
|
||||
|
||||
if (!\is_object($this->aes)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->aes->setPassword($this->getPassword(), $legacy);
|
||||
$decrypted = $this->aes->decryptString($data, true);
|
||||
|
||||
// Decrypted data is null byte padded. We have to remove the padding before proceeding.
|
||||
return rtrim($decrypted, "\0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the AES cryptography object
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function initialize(): void
|
||||
{
|
||||
if (\is_object($this->aes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$password = $this->getPassword();
|
||||
|
||||
if (empty($password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->aes = new Aes('cbc');
|
||||
$this->aes->setPassword($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password used to encrypt information in the component
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function getPassword(): string
|
||||
{
|
||||
try {
|
||||
return Factory::getApplication()->get('secret', '');
|
||||
} catch (\Exception $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
425
administrator/components/com_users/src/Service/HTML/Users.php
Normal file
425
administrator/components/com_users/src/Service/HTML/Users.php
Normal file
@ -0,0 +1,425 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Service\HTML;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\LanguageHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Filesystem\Path;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Extended Utility class for the Users component.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class Users
|
||||
{
|
||||
/**
|
||||
* Display an image.
|
||||
*
|
||||
* @param string $src The source of the image
|
||||
*
|
||||
* @return string A <img> element if the specified file exists, otherwise, a null string
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function image($src)
|
||||
{
|
||||
$src = preg_replace('#[^A-Z0-9\-_\./]#i', '', $src);
|
||||
$file = JPATH_SITE . '/' . $src;
|
||||
|
||||
Path::check($file);
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<img src="' . Uri::root() . $src . '" alt="">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an icon to add a note for this user.
|
||||
*
|
||||
* @param integer $userId The user ID
|
||||
*
|
||||
* @return string A link to add a note
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function addNote($userId)
|
||||
{
|
||||
$title = Text::_('COM_USERS_ADD_NOTE');
|
||||
|
||||
return '<a href="' . Route::_('index.php?option=com_users&task=note.add&u_id=' . (int) $userId)
|
||||
. '" class="btn btn-secondary btn-sm"><span class="icon-plus pe-1" aria-hidden="true">'
|
||||
. '</span>' . $title . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an icon to filter the notes list on this user.
|
||||
*
|
||||
* @param integer $count The number of notes for the user
|
||||
* @param integer $userId The user ID
|
||||
*
|
||||
* @return string A link to apply a filter
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function filterNotes($count, $userId)
|
||||
{
|
||||
if (empty($count)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$title = Text::_('COM_USERS_FILTER_NOTES');
|
||||
|
||||
return '<a href="' . Route::_('index.php?option=com_users&view=notes&filter[search]=uid:' . (int) $userId)
|
||||
. '" class="dropdown-item"><span class="icon-list pe-1" aria-hidden="true"></span>' . $title . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a note icon.
|
||||
*
|
||||
* @param integer $count The number of notes for the user
|
||||
* @param integer $userId The user ID
|
||||
*
|
||||
* @return string A link to a modal window with the user notes
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function notes($count, $userId)
|
||||
{
|
||||
if (empty($count)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$title = Text::plural('COM_USERS_N_USER_NOTES', $count);
|
||||
|
||||
return '<button type="button" data-bs-target="#userModal_' . (int) $userId . '" id="modal-' . (int) $userId
|
||||
. '" data-bs-toggle="modal" class="dropdown-item"><span class="icon-eye pe-1" aria-hidden="true"></span>' . $title . '</button>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the modal html.
|
||||
*
|
||||
* @param integer $count The number of notes for the user
|
||||
* @param integer $userId The user ID
|
||||
*
|
||||
* @return string The html for the rendered modal
|
||||
*
|
||||
* @since 3.4.1
|
||||
*/
|
||||
public function notesModal($count, $userId)
|
||||
{
|
||||
if (empty($count)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$title = Text::plural('COM_USERS_N_USER_NOTES', $count);
|
||||
$footer = '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">'
|
||||
. Text::_('JTOOLBAR_CLOSE') . '</button>';
|
||||
|
||||
return HTMLHelper::_(
|
||||
'bootstrap.renderModal',
|
||||
'userModal_' . (int) $userId,
|
||||
[
|
||||
'title' => $title,
|
||||
'backdrop' => 'static',
|
||||
'keyboard' => true,
|
||||
'closeButton' => true,
|
||||
'footer' => $footer,
|
||||
'url' => Route::_('index.php?option=com_users&view=notes&tmpl=component&layout=modal&filter[user_id]=' . (int) $userId),
|
||||
'height' => '300px',
|
||||
'width' => '800px',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array of block/unblock user states to be used by jgrid.state,
|
||||
* State options will be different for any user
|
||||
* and for currently logged in user
|
||||
*
|
||||
* @param boolean $self True if state array is for currently logged in user
|
||||
*
|
||||
* @return array a list of possible states to display
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function blockStates($self = false)
|
||||
{
|
||||
if ($self) {
|
||||
$states = [
|
||||
1 => [
|
||||
'task' => 'unblock',
|
||||
'text' => '',
|
||||
'active_title' => 'COM_USERS_TOOLBAR_BLOCK',
|
||||
'inactive_title' => '',
|
||||
'tip' => true,
|
||||
'active_class' => 'unpublish',
|
||||
'inactive_class' => 'unpublish',
|
||||
],
|
||||
0 => [
|
||||
'task' => 'block',
|
||||
'text' => '',
|
||||
'active_title' => '',
|
||||
'inactive_title' => 'COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF',
|
||||
'tip' => true,
|
||||
'active_class' => 'publish',
|
||||
'inactive_class' => 'publish',
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$states = [
|
||||
1 => [
|
||||
'task' => 'unblock',
|
||||
'text' => '',
|
||||
'active_title' => 'COM_USERS_TOOLBAR_UNBLOCK',
|
||||
'inactive_title' => '',
|
||||
'tip' => true,
|
||||
'active_class' => 'unpublish',
|
||||
'inactive_class' => 'unpublish',
|
||||
],
|
||||
0 => [
|
||||
'task' => 'block',
|
||||
'text' => '',
|
||||
'active_title' => 'COM_USERS_TOOLBAR_BLOCK',
|
||||
'inactive_title' => '',
|
||||
'tip' => true,
|
||||
'active_class' => 'publish',
|
||||
'inactive_class' => 'publish',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array of activate states to be used by jgrid.state,
|
||||
*
|
||||
* @return array a list of possible states to display
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function activateStates()
|
||||
{
|
||||
$states = [
|
||||
1 => [
|
||||
'task' => 'activate',
|
||||
'text' => '',
|
||||
'active_title' => 'COM_USERS_TOOLBAR_ACTIVATE',
|
||||
'inactive_title' => '',
|
||||
'tip' => true,
|
||||
'active_class' => 'unpublish',
|
||||
'inactive_class' => 'unpublish',
|
||||
],
|
||||
0 => [
|
||||
'task' => '',
|
||||
'text' => '',
|
||||
'active_title' => '',
|
||||
'inactive_title' => 'COM_USERS_ACTIVATED',
|
||||
'tip' => true,
|
||||
'active_class' => 'publish',
|
||||
'inactive_class' => 'publish',
|
||||
],
|
||||
];
|
||||
|
||||
return $states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sanitized value
|
||||
*
|
||||
* @param mixed $value Value of the field
|
||||
*
|
||||
* @return mixed String/void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function value($value)
|
||||
{
|
||||
if (\is_string($value)) {
|
||||
$value = trim($value);
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
return Text::_('COM_USERS_PROFILE_VALUE_NOT_FOUND');
|
||||
}
|
||||
|
||||
if (!\is_array($value)) {
|
||||
return htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space symbol
|
||||
*
|
||||
* @param mixed $value Value of the field
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function spacer($value)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sanitized template style
|
||||
*
|
||||
* @param mixed $value Value of the field
|
||||
*
|
||||
* @return mixed String/void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function templatestyle($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return static::value($value);
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('title'))
|
||||
->from($db->quoteName('#__template_styles'))
|
||||
->where($db->quoteName('id') . ' = :id')
|
||||
->bind(':id', $value, ParameterType::INTEGER);
|
||||
$db->setQuery($query);
|
||||
$title = $db->loadResult();
|
||||
|
||||
if ($title) {
|
||||
return htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
|
||||
return static::value('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sanitized language
|
||||
*
|
||||
* @param mixed $value Value of the field
|
||||
*
|
||||
* @return mixed String/void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function admin_language($value)
|
||||
{
|
||||
if (!$value) {
|
||||
return static::value($value);
|
||||
}
|
||||
|
||||
$path = LanguageHelper::getLanguagePath(JPATH_ADMINISTRATOR, $value);
|
||||
$file = $path . '/langmetadata.xml';
|
||||
|
||||
if (!is_file($file)) {
|
||||
// For language packs from before 4.0.
|
||||
$file = $path . '/' . $value . '.xml';
|
||||
|
||||
if (!is_file($file)) {
|
||||
return static::value($value);
|
||||
}
|
||||
}
|
||||
|
||||
$result = LanguageHelper::parseXMLLanguageFile($file);
|
||||
|
||||
if ($result) {
|
||||
return htmlspecialchars($result['name'], ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
|
||||
return static::value($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sanitized language
|
||||
*
|
||||
* @param mixed $value Value of the field
|
||||
*
|
||||
* @return mixed String/void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function language($value)
|
||||
{
|
||||
if (!$value) {
|
||||
return static::value($value);
|
||||
}
|
||||
|
||||
$path = LanguageHelper::getLanguagePath(JPATH_SITE, $value);
|
||||
$file = $path . '/langmetadata.xml';
|
||||
|
||||
if (!is_file($file)) {
|
||||
// For language packs from before 4.0.
|
||||
$file = $path . '/' . $value . '.xml';
|
||||
|
||||
if (!is_file($file)) {
|
||||
return static::value($value);
|
||||
}
|
||||
}
|
||||
|
||||
$result = LanguageHelper::parseXMLLanguageFile($file);
|
||||
|
||||
if ($result) {
|
||||
return htmlspecialchars($result['name'], ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
|
||||
return static::value($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sanitized editor name
|
||||
*
|
||||
* @param mixed $value Value of the field
|
||||
*
|
||||
* @return mixed String/void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function editor($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return static::value($value);
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$lang = Factory::getLanguage();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('name'))
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('element') . ' = :element')
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('editors'))
|
||||
->bind(':element', $value);
|
||||
$db->setQuery($query);
|
||||
$title = $db->loadResult();
|
||||
|
||||
if ($title) {
|
||||
$lang->load("plg_editors_$value.sys", JPATH_ADMINISTRATOR)
|
||||
|| $lang->load("plg_editors_$value.sys", JPATH_PLUGINS . '/editors/' . $value);
|
||||
$lang->load($title . '.sys');
|
||||
|
||||
return Text::_($title);
|
||||
}
|
||||
|
||||
return static::value('');
|
||||
}
|
||||
}
|
||||
427
administrator/components/com_users/src/Table/MfaTable.php
Normal file
427
administrator/components/com_users/src/Table/MfaTable.php
Normal file
@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Table;
|
||||
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\CMS\Table\Table;
|
||||
use Joomla\CMS\User\CurrentUserInterface;
|
||||
use Joomla\CMS\User\CurrentUserTrait;
|
||||
use Joomla\CMS\User\UserFactoryAwareInterface;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
|
||||
use Joomla\Component\Users\Administrator\Service\Encrypt;
|
||||
use Joomla\Database\DatabaseDriver;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Table for the Multi-Factor Authentication records
|
||||
*
|
||||
* @property int $id Record ID.
|
||||
* @property int $user_id User ID
|
||||
* @property string $title Record title.
|
||||
* @property string $method MFA Method (corresponds to one of the plugins).
|
||||
* @property int $default Is this the default Method?
|
||||
* @property array $options Configuration options for the MFA Method.
|
||||
* @property string $created_on Date and time the record was created.
|
||||
* @property string $last_used Date and time the record was last used successfully.
|
||||
* @property int $tries Counter for unsuccessful tries
|
||||
* @property string $last_try Date and time of the last unsuccessful try
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class MfaTable extends Table implements CurrentUserInterface, UserFactoryAwareInterface
|
||||
{
|
||||
use CurrentUserTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* Delete flags per ID, set up onBeforeDelete and used onAfterDelete
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $deleteFlags = [];
|
||||
|
||||
/**
|
||||
* Encryption service
|
||||
*
|
||||
* @var Encrypt
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $encryptService;
|
||||
|
||||
/**
|
||||
* Indicates that columns fully support the NULL value in the database
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
// phpcs:ignore
|
||||
protected $_supportNullValue = true;
|
||||
|
||||
/**
|
||||
* Table constructor
|
||||
*
|
||||
* @param DatabaseDriver $db Database driver object
|
||||
* @param ?DispatcherInterface $dispatcher Events dispatcher object
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(DatabaseDriver $db, DispatcherInterface $dispatcher = null)
|
||||
{
|
||||
parent::__construct('#__user_mfa', 'id', $db, $dispatcher);
|
||||
|
||||
$this->encryptService = new Encrypt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to store a row in the database from the Table instance properties.
|
||||
*
|
||||
* If a primary key value is set the row with that primary key value will be updated with the instance property values.
|
||||
* If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
|
||||
*
|
||||
* @param boolean $updateNulls True to update fields even if they are null.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function store($updateNulls = true)
|
||||
{
|
||||
// Encrypt the options before saving them
|
||||
$this->options = $this->encryptService->encrypt(json_encode($this->options ?: []));
|
||||
|
||||
// Set last_used date to null if empty or zero date
|
||||
if (!((int) $this->last_used)) {
|
||||
$this->last_used = null;
|
||||
}
|
||||
|
||||
$records = MfaHelper::getUserMfaRecords($this->user_id);
|
||||
|
||||
if ($this->id) {
|
||||
// Existing record. Remove it from the list of records.
|
||||
$records = array_filter(
|
||||
$records,
|
||||
function ($rec) {
|
||||
return $rec->id != $this->id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Update the dates on a new record
|
||||
if (empty($this->id)) {
|
||||
$this->created_on = Date::getInstance()->toSql();
|
||||
$this->last_used = null;
|
||||
}
|
||||
|
||||
// Do I need to mark this record as the default?
|
||||
if ($this->default == 0) {
|
||||
$hasDefaultRecord = array_reduce(
|
||||
$records,
|
||||
function ($carry, $record) {
|
||||
return $carry || ($record->default == 1);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
$this->default = $hasDefaultRecord ? 0 : 1;
|
||||
}
|
||||
|
||||
// Let's find out if we are saving a new MFA method record without having backup codes yet.
|
||||
$mustCreateBackupCodes = false;
|
||||
|
||||
if (empty($this->id) && $this->method !== 'backupcodes') {
|
||||
// Do I have any backup records?
|
||||
$hasBackupCodes = array_reduce(
|
||||
$records,
|
||||
function (bool $carry, $record) {
|
||||
return $carry || $record->method === 'backupcodes';
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
$mustCreateBackupCodes = !$hasBackupCodes;
|
||||
|
||||
// If the only other entry is the backup records one I need to make this the default method
|
||||
if ($hasBackupCodes && \count($records) === 1) {
|
||||
$this->default = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the record
|
||||
try {
|
||||
$result = parent::store($updateNulls);
|
||||
} catch (\Throwable $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
$result = false;
|
||||
}
|
||||
|
||||
// Decrypt the options (they must be decrypted in memory)
|
||||
$this->decryptOptions();
|
||||
|
||||
if ($result) {
|
||||
// If this record is the default unset the default flag from all other records
|
||||
$this->switchDefaultRecord();
|
||||
|
||||
// Do I need to generate backup codes?
|
||||
if ($mustCreateBackupCodes) {
|
||||
$this->generateBackupCodes();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to load a row from the database by primary key and bind the fields to the Table instance properties.
|
||||
*
|
||||
* @param mixed $keys An optional primary key value to load the row by, or an array of fields to match.
|
||||
* If not set the instance property value is used.
|
||||
* @param boolean $reset True to reset the default values before loading the new row.
|
||||
*
|
||||
* @return boolean True if successful. False if row not found.
|
||||
*
|
||||
* @since 4.2.0
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function load($keys = null, $reset = true)
|
||||
{
|
||||
$result = parent::load($keys, $reset);
|
||||
|
||||
if ($result) {
|
||||
$this->decryptOptions();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to delete a row from the database table by primary key value.
|
||||
*
|
||||
* @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 4.2.0
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function delete($pk = null)
|
||||
{
|
||||
$record = $this;
|
||||
|
||||
if ($pk != $this->id) {
|
||||
$record = clone $this;
|
||||
$record->reset();
|
||||
$result = $record->load($pk);
|
||||
|
||||
if (!$result) {
|
||||
// If the record does not exist I will stomp my feet and deny your request
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
// The user must be a registered user, not a guest
|
||||
if ($user->guest) {
|
||||
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
// Save flags used onAfterDelete
|
||||
$this->deleteFlags[$record->id] = [
|
||||
'default' => $record->default,
|
||||
'numRecords' => $this->getNumRecords($record->user_id),
|
||||
'user_id' => $record->user_id,
|
||||
'method' => $record->method,
|
||||
];
|
||||
|
||||
if (\is_null($pk)) {
|
||||
$pk = [$this->_tbl_key => $this->id];
|
||||
} elseif (!\is_array($pk)) {
|
||||
$pk = [$this->_tbl_key => $pk];
|
||||
}
|
||||
|
||||
$isDeleted = parent::delete($pk);
|
||||
|
||||
if ($isDeleted) {
|
||||
$this->afterDelete($pk);
|
||||
}
|
||||
|
||||
return $isDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the possibly encrypted options
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function decryptOptions(): void
|
||||
{
|
||||
// Try with modern decryption
|
||||
$decrypted = @json_decode($this->encryptService->decrypt($this->options ?? ''), true);
|
||||
|
||||
if (\is_string($decrypted)) {
|
||||
$decrypted = @json_decode($decrypted, true);
|
||||
}
|
||||
|
||||
// Fall back to legacy decryption
|
||||
if (!\is_array($decrypted)) {
|
||||
$decrypted = @json_decode($this->encryptService->decrypt($this->options ?? '', true), true);
|
||||
|
||||
if (\is_string($decrypted)) {
|
||||
$decrypted = @json_decode($decrypted, true);
|
||||
}
|
||||
}
|
||||
|
||||
$this->options = $decrypted ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* If this record is set to be the default, unset the default flag from the other records for the same user.
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function switchDefaultRecord(): void
|
||||
{
|
||||
if (!$this->default) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This record is marked as default, therefore we need to unset the default flag from all other records for this
|
||||
* user.
|
||||
*/
|
||||
$db = $this->getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__user_mfa'))
|
||||
->set($db->quoteName('default') . ' = 0')
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->where($db->quoteName('id') . ' != :id')
|
||||
->bind(':user_id', $this->user_id, ParameterType::INTEGER)
|
||||
->bind(':id', $this->id, ParameterType::INTEGER);
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate backup code is the flag is set.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function generateBackupCodes(): void
|
||||
{
|
||||
/** @var MVCFactoryInterface $factory */
|
||||
$factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
|
||||
|
||||
/** @var BackupcodesModel $backupCodes */
|
||||
$backupCodes = $factory->createModel('Backupcodes', 'Administrator');
|
||||
$user = $this->getUserFactory()->loadUserById($this->user_id);
|
||||
$backupCodes->regenerateBackupCodes($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after successfully deleting a record
|
||||
*
|
||||
* @param int|array $pk The promary key of the deleted record
|
||||
*
|
||||
* @return void
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function afterDelete($pk): void
|
||||
{
|
||||
if (\is_array($pk)) {
|
||||
$pk = $pk[$this->_tbl_key] ?? array_shift($pk);
|
||||
}
|
||||
|
||||
if (!isset($this->deleteFlags[$pk])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (($this->deleteFlags[$pk]['numRecords'] <= 2) && ($this->deleteFlags[$pk]['method'] != 'backupcodes')) {
|
||||
/**
|
||||
* This was the second to last MFA record in the database (the last one is the `backupcodes`). Therefore, we
|
||||
* need to delete the remaining entry and go away. We don't trigger this if the Method we are deleting was
|
||||
* the `backupcodes` because we might just be regenerating the backup codes.
|
||||
*/
|
||||
$db = $this->getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->delete($db->quoteName('#__user_mfa'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->bind(':user_id', $this->deleteFlags[$pk]['user_id'], ParameterType::INTEGER);
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
unset($this->deleteFlags[$pk]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This was the default record. Promote the next available record to default.
|
||||
if ($this->deleteFlags[$pk]['default']) {
|
||||
$db = $this->getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName('#__user_mfa'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->where($db->quoteName('method') . ' != ' . $db->quote('backupcodes'))
|
||||
->bind(':user_id', $this->deleteFlags[$pk]['user_id'], ParameterType::INTEGER);
|
||||
$ids = $db->setQuery($query)->loadColumn();
|
||||
|
||||
if (empty($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = array_shift($ids);
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__user_mfa'))
|
||||
->set($db->quoteName('default') . ' = 1')
|
||||
->where($db->quoteName('id') . ' = :id')
|
||||
->bind(':id', $id, ParameterType::INTEGER);
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of MFA records for a give user ID
|
||||
*
|
||||
* @param int $userId The user ID to check
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function getNumRecords(int $userId): int
|
||||
{
|
||||
$db = $this->getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__user_mfa'))
|
||||
->where($db->quoteName('user_id') . ' = :user_id')
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER);
|
||||
$numOldRecords = $db->setQuery($query)->loadResult();
|
||||
|
||||
return (int) $numOldRecords;
|
||||
}
|
||||
}
|
||||
131
administrator/components/com_users/src/Table/NoteTable.php
Normal file
131
administrator/components/com_users/src/Table/NoteTable.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\Table;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Table\Table;
|
||||
use Joomla\CMS\User\CurrentUserInterface;
|
||||
use Joomla\CMS\User\CurrentUserTrait;
|
||||
use Joomla\CMS\Versioning\VersionableTableInterface;
|
||||
use Joomla\Database\DatabaseDriver;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User notes table class
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class NoteTable extends Table implements VersionableTableInterface, CurrentUserInterface
|
||||
{
|
||||
use CurrentUserTrait;
|
||||
|
||||
/**
|
||||
* Indicates that columns fully support the NULL value in the database
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $_supportNullValue = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DatabaseDriver $db Database connector object
|
||||
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function __construct(DatabaseDriver $db, DispatcherInterface $dispatcher = null)
|
||||
{
|
||||
$this->typeAlias = 'com_users.note';
|
||||
parent::__construct('#__user_notes', 'id', $db, $dispatcher);
|
||||
|
||||
$this->setColumnAlias('published', 'state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded store method for the notes table.
|
||||
*
|
||||
* @param boolean $updateNulls Toggle whether null values should be updated.
|
||||
*
|
||||
* @return boolean True on success, false on failure.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function store($updateNulls = true)
|
||||
{
|
||||
$date = Factory::getDate()->toSql();
|
||||
$userId = $this->getCurrentUser()->get('id');
|
||||
|
||||
if (!((int) $this->review_time)) {
|
||||
$this->review_time = null;
|
||||
}
|
||||
|
||||
if ($this->id) {
|
||||
// Existing item
|
||||
$this->modified_time = $date;
|
||||
$this->modified_user_id = $userId;
|
||||
} else {
|
||||
// New record.
|
||||
$this->created_time = $date;
|
||||
$this->created_user_id = $userId;
|
||||
$this->modified_time = $date;
|
||||
$this->modified_user_id = $userId;
|
||||
}
|
||||
|
||||
// Attempt to store the data.
|
||||
return parent::store($updateNulls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to perform sanity checks on the Table instance properties to ensure they are safe to store in the database.
|
||||
*
|
||||
* @return boolean True if the instance is sane and able to be stored in the database.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
try {
|
||||
parent::check();
|
||||
} catch (\Exception $e) {
|
||||
$this->setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->modified_time)) {
|
||||
$this->modified_time = $this->created_time;
|
||||
}
|
||||
|
||||
if (empty($this->modified_user_id)) {
|
||||
$this->modified_user_id = $this->created_user_id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type alias for the history table
|
||||
*
|
||||
* @return string The alias as described above
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getTypeAlias()
|
||||
{
|
||||
return $this->typeAlias;
|
||||
}
|
||||
}
|
||||
219
administrator/components/com_users/src/View/Captive/HtmlView.php
Normal file
219
administrator/components/com_users/src/View/Captive/HtmlView.php
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Captive;
|
||||
|
||||
use Joomla\CMS\Event\MultiFactor\BeforeDisplayMethods;
|
||||
use Joomla\CMS\Event\MultiFactor\NotifyActionLog;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Toolbar\Button\BasicButton;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
|
||||
use Joomla\Component\Users\Administrator\Model\CaptiveModel;
|
||||
use Joomla\Component\Users\Administrator\View\SiteTemplateTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View for Multi-factor Authentication captive page
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
use SiteTemplateTrait;
|
||||
|
||||
/**
|
||||
* The MFA Method records for the current user which correspond to enabled plugins
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $records = [];
|
||||
|
||||
/**
|
||||
* The currently selected MFA Method record against which we'll be authenticating
|
||||
*
|
||||
* @var null|\stdClass
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $record = null;
|
||||
|
||||
/**
|
||||
* The Captive MFA page's rendering options
|
||||
*
|
||||
* @var array|null
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $renderOptions = null;
|
||||
|
||||
/**
|
||||
* The title to display at the top of the page
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Is this an administrator page?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $isAdmin = false;
|
||||
|
||||
/**
|
||||
* Does the currently selected Method allow authenticating against all of its records?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $allowEntryBatching = false;
|
||||
|
||||
/**
|
||||
* All enabled MFA Methods (plugins)
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $mfaMethods;
|
||||
|
||||
/**
|
||||
* Execute and display a template script.
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void A string if successful, otherwise an Error object.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$this->setSiteTemplateStyle();
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
PluginHelper::importPlugin('multifactorauth');
|
||||
$event = new BeforeDisplayMethods($user);
|
||||
$app->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
/** @var CaptiveModel $model */
|
||||
$model = $this->getModel();
|
||||
|
||||
// Load data from the model
|
||||
$this->isAdmin = $app->isClient('administrator');
|
||||
$this->records = $this->get('records');
|
||||
$this->record = $this->get('record');
|
||||
$this->mfaMethods = MfaHelper::getMfaMethods();
|
||||
|
||||
if (!empty($this->records)) {
|
||||
/** @var BackupcodesModel $codesModel */
|
||||
$codesModel = $this->getModel('Backupcodes');
|
||||
$backupCodesRecord = $codesModel->getBackupCodesRecord();
|
||||
|
||||
if (!\is_null($backupCodesRecord)) {
|
||||
$backupCodesRecord->title = Text::_('COM_USERS_USER_BACKUPCODES');
|
||||
$this->records[] = $backupCodesRecord;
|
||||
}
|
||||
}
|
||||
|
||||
// If we only have one record there's no point asking the user to select a MFA Method
|
||||
if (empty($this->record) && !empty($this->records)) {
|
||||
// Default to the first record
|
||||
$this->record = reset($this->records);
|
||||
|
||||
// If we have multiple records try to make this record the default
|
||||
if (\count($this->records) > 1) {
|
||||
foreach ($this->records as $record) {
|
||||
if ($record->default) {
|
||||
$this->record = $record;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the correct layout based on the availability of a MFA record
|
||||
$this->setLayout('default');
|
||||
|
||||
// If we have no record selected or explicitly asked to run the 'select' task use the correct layout
|
||||
if (\is_null($this->record) || ($model->getState('task') == 'select')) {
|
||||
$this->setLayout('select');
|
||||
}
|
||||
|
||||
switch ($this->getLayout()) {
|
||||
case 'select':
|
||||
$this->allowEntryBatching = 1;
|
||||
|
||||
$event = new NotifyActionLog('onComUsersCaptiveShowSelect', []);
|
||||
Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
|
||||
break;
|
||||
|
||||
case 'default':
|
||||
default:
|
||||
$this->renderOptions = $model->loadCaptiveRenderOptions($this->record);
|
||||
$this->allowEntryBatching = $this->renderOptions['allowEntryBatching'] ?? 0;
|
||||
|
||||
$event = new NotifyActionLog(
|
||||
'onComUsersCaptiveShowCaptive',
|
||||
[
|
||||
$this->escape($this->record->title),
|
||||
]
|
||||
);
|
||||
Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
|
||||
break;
|
||||
}
|
||||
|
||||
// Which title should I use for the page?
|
||||
$this->title = $this->get('PageTitle');
|
||||
|
||||
// Back-end: always show a title in the 'title' module position, not in the page body
|
||||
if ($this->isAdmin) {
|
||||
ToolbarHelper::title(Text::_('COM_USERS_USER_MULTIFACTOR_AUTH'), 'users user-lock');
|
||||
$this->title = '';
|
||||
}
|
||||
|
||||
if ($this->isAdmin && $this->getLayout() === 'default') {
|
||||
$bar = Toolbar::getInstance();
|
||||
$button = (new BasicButton('user-mfa-submit'))
|
||||
->text($this->renderOptions['submit_text'])
|
||||
->icon($this->renderOptions['submit_icon']);
|
||||
$bar->appendButton($button);
|
||||
|
||||
$button = (new BasicButton('user-mfa-logout'))
|
||||
->text('COM_USERS_MFA_LOGOUT')
|
||||
->buttonClass('btn btn-danger')
|
||||
->icon('icon icon-lock');
|
||||
$bar->appendButton($button);
|
||||
|
||||
if (\count($this->records) > 1) {
|
||||
$arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
|
||||
$button = (new BasicButton('user-mfa-choose-another'))
|
||||
->text('COM_USERS_MFA_USE_DIFFERENT_METHOD')
|
||||
->icon('icon-' . $arrow);
|
||||
$bar->appendButton($button);
|
||||
}
|
||||
}
|
||||
|
||||
// Display the view
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Debuggroup;
|
||||
|
||||
use Joomla\CMS\Access\Exception\NotAllowed;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View class for a list of User Group ACL permissions.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* List of component actions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $actions;
|
||||
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* The pagination object.
|
||||
*
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The id and title for the user group.
|
||||
*
|
||||
* @var \stdClass
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* Form object for search filters
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* The active search filters
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
// Access check.
|
||||
if (!$this->getCurrentUser()->authorise('core.manage', 'com_users')) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
$this->actions = $this->get('DebugActions');
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->group = $this->get('Group');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::sprintf('COM_USERS_VIEW_DEBUG_GROUP_TITLE', $this->group->id, $this->escape($this->group->title)), 'users groups');
|
||||
$toolbar->cancel('group.cancel');
|
||||
|
||||
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
|
||||
$toolbar->preferences('com_users');
|
||||
$toolbar->divider();
|
||||
}
|
||||
|
||||
$toolbar->help('Permissions_for_Group');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Debuguser;
|
||||
|
||||
use Joomla\CMS\Access\Exception\NotAllowed;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\CMS\User\User;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View class for a list of User ACL permissions.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* List of component actions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $actions;
|
||||
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* The pagination object.
|
||||
*
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The user object of the user being debugged.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* Form object for search filters
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* The active search filters
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
// Access check.
|
||||
if (!$this->getCurrentUser()->authorise('core.manage', 'com_users')) {
|
||||
throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
$this->actions = $this->get('DebugActions');
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->user = $this->get('User');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::sprintf('COM_USERS_VIEW_DEBUG_USER_TITLE', $this->user->id, $this->escape($this->user->name)), 'users user');
|
||||
$toolbar->cancel('user.cancel');
|
||||
|
||||
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
|
||||
$toolbar->preferences('com_users');
|
||||
$toolbar->divider();
|
||||
}
|
||||
|
||||
$toolbar->help('Permissions_for_User');
|
||||
}
|
||||
}
|
||||
126
administrator/components/com_users/src/View/Group/HtmlView.php
Normal file
126
administrator/components/com_users/src/View/Group/HtmlView.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Group;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View to edit a user group.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The Form object
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$this->state = $this->get('State');
|
||||
$this->item = $this->get('Item');
|
||||
$this->form = $this->get('Form');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
Factory::getApplication()->getInput()->set('hidemainmenu', true);
|
||||
|
||||
$isNew = ($this->item->id == 0);
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::_($isNew ? 'COM_USERS_VIEW_NEW_GROUP_TITLE' : 'COM_USERS_VIEW_EDIT_GROUP_TITLE'), 'users-cog groups-add');
|
||||
|
||||
if ($canDo->get('core.edit') || $canDo->get('core.create')) {
|
||||
$toolbar->apply('group.apply');
|
||||
}
|
||||
|
||||
$saveGroup = $toolbar->dropdownButton('save-group');
|
||||
$saveGroup->configure(
|
||||
function (Toolbar $childBar) use ($canDo, $isNew) {
|
||||
if ($canDo->get('core.edit') || $canDo->get('core.create')) {
|
||||
$childBar->save('group.save');
|
||||
}
|
||||
|
||||
if ($canDo->get('core.create')) {
|
||||
$childBar->save2new('group.save2new');
|
||||
}
|
||||
|
||||
// If an existing item, can save to a copy.
|
||||
if (!$isNew && $canDo->get('core.create')) {
|
||||
$childBar->save2copy('group.save2copy');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (empty($this->item->id)) {
|
||||
$toolbar->cancel('group.cancel', 'JTOOLBAR_CANCEL');
|
||||
} else {
|
||||
$toolbar->cancel('group.cancel');
|
||||
}
|
||||
|
||||
$toolbar->divider();
|
||||
$toolbar->help('Users:_New_or_Edit_Group');
|
||||
}
|
||||
}
|
||||
127
administrator/components/com_users/src/View/Groups/HtmlView.php
Normal file
127
administrator/components/com_users/src/View/Groups/HtmlView.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Groups;
|
||||
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View class for a list of user groups.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* The pagination object.
|
||||
*
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Form object for search filters
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* The active search filters
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::_('COM_USERS_VIEW_GROUPS_TITLE'), 'users-cog groups');
|
||||
|
||||
if ($canDo->get('core.create')) {
|
||||
$toolbar->addNew('group.add');
|
||||
}
|
||||
|
||||
if ($canDo->get('core.delete')) {
|
||||
$toolbar->delete('groups.delete')
|
||||
->message('JGLOBAL_CONFIRM_DELETE');
|
||||
$toolbar->divider();
|
||||
}
|
||||
|
||||
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
|
||||
$toolbar->preferences('com_users');
|
||||
$toolbar->divider();
|
||||
}
|
||||
|
||||
$toolbar->help('Users:_Groups');
|
||||
}
|
||||
}
|
||||
126
administrator/components/com_users/src/View/Level/HtmlView.php
Normal file
126
administrator/components/com_users/src/View/Level/HtmlView.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Level;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View to edit a user view level.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The Form object
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$this->form = $this->get('Form');
|
||||
$this->item = $this->get('Item');
|
||||
$this->state = $this->get('State');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
Factory::getApplication()->getInput()->set('hidemainmenu', true);
|
||||
|
||||
$isNew = ($this->item->id == 0);
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::_($isNew ? 'COM_USERS_VIEW_NEW_LEVEL_TITLE' : 'COM_USERS_VIEW_EDIT_LEVEL_TITLE'), 'user-lock levels-add');
|
||||
|
||||
if ($canDo->get('core.edit') || $canDo->get('core.create')) {
|
||||
$toolbar->apply('level.apply');
|
||||
}
|
||||
|
||||
$saveGroup = $toolbar->dropdownButton('save-group');
|
||||
$saveGroup->configure(
|
||||
function (Toolbar $childBar) use ($canDo, $isNew) {
|
||||
if ($canDo->get('core.edit') || $canDo->get('core.create')) {
|
||||
$childBar->save('level.save');
|
||||
}
|
||||
|
||||
if ($canDo->get('core.create')) {
|
||||
$childBar->save2new('level.save2new');
|
||||
}
|
||||
|
||||
// If an existing item, can save to a copy.
|
||||
if (!$isNew && $canDo->get('core.create')) {
|
||||
$childBar->save2copy('level.save2copy');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (empty($this->item->id)) {
|
||||
$toolbar->cancel('level.cancel', 'JTOOLBAR_CANCEL');
|
||||
} else {
|
||||
$toolbar->cancel('level.cancel');
|
||||
}
|
||||
|
||||
$toolbar->divider();
|
||||
$toolbar->help('Users:_Edit_Viewing_Access_Level');
|
||||
}
|
||||
}
|
||||
127
administrator/components/com_users/src/View/Levels/HtmlView.php
Normal file
127
administrator/components/com_users/src/View/Levels/HtmlView.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Levels;
|
||||
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View class for a list of view levels.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* The pagination object.
|
||||
*
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Form object for search filters
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* The active search filters
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::_('COM_USERS_VIEW_LEVELS_TITLE'), 'user-lock levels');
|
||||
|
||||
if ($canDo->get('core.create')) {
|
||||
$toolbar->addNew('level.add');
|
||||
}
|
||||
|
||||
if ($canDo->get('core.delete')) {
|
||||
$toolbar->delete('level.delete')
|
||||
->message('JGLOBAL_CONFIRM_DELETE');
|
||||
$toolbar->divider();
|
||||
}
|
||||
|
||||
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
|
||||
$toolbar->preferences('com_users');
|
||||
$toolbar->divider();
|
||||
}
|
||||
|
||||
$toolbar->help('Users:_Viewing_Access_Levels');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Mail;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Users mail view.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The Form object
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
// Redirect to admin index if mass mailer disabled in conf
|
||||
if (Factory::getApplication()->get('massmailoff', 0) == 1) {
|
||||
Factory::getApplication()->redirect(Route::_('index.php', false));
|
||||
}
|
||||
|
||||
// Get data from the model
|
||||
$this->form = $this->get('Form');
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
Factory::getApplication()->getInput()->set('hidemainmenu', true);
|
||||
|
||||
ToolbarHelper::title(Text::_('COM_USERS_MASS_MAIL'), 'users massmail');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
$toolbar->standardButton('COM_USERS_TOOLBAR_MAIL_SEND_MAIL', 'COM_USERS_TOOLBAR_MAIL_SEND_MAIL', 'mail.send')
|
||||
->icon('icon-envelope')
|
||||
->formValidation(true);
|
||||
|
||||
$toolbar->cancel('mail.cancel', 'JTOOLBAR_CANCEL');
|
||||
$toolbar->divider();
|
||||
$toolbar->preferences('com_users');
|
||||
$toolbar->divider();
|
||||
$toolbar->help('Mass_Mail_Users');
|
||||
}
|
||||
}
|
||||
220
administrator/components/com_users/src/View/Method/HtmlView.php
Normal file
220
administrator/components/com_users/src/View/Method/HtmlView.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Method;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Toolbar\Button\BasicButton;
|
||||
use Joomla\CMS\Toolbar\Button\LinkButton;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\Model\MethodModel;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View for Multi-factor Authentication method add/edit page
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* Is this an administrator page?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $isAdmin = false;
|
||||
|
||||
/**
|
||||
* The editor page render options
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $renderOptions = [];
|
||||
|
||||
/**
|
||||
* The MFA Method record being edited
|
||||
*
|
||||
* @var object
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $record = null;
|
||||
|
||||
/**
|
||||
* The title text for this page
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* The return URL to use for all links and forms
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $returnURL = null;
|
||||
|
||||
/**
|
||||
* The user object used to display this page
|
||||
*
|
||||
* @var User
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $user = null;
|
||||
|
||||
/**
|
||||
* The backup codes for the current user. Only applies when the backup codes record is being "edited"
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $backupCodes = [];
|
||||
|
||||
/**
|
||||
* Am I editing an existing Method? If it's false then I'm adding a new Method.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $isEditExisting = false;
|
||||
|
||||
/**
|
||||
* Execute and display a template script.
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \JViewLegacy::loadTemplate()
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if (empty($this->user)) {
|
||||
$this->user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
/** @var MethodModel $model */
|
||||
$model = $this->getModel();
|
||||
$this->setLayout('edit');
|
||||
$this->renderOptions = $model->getRenderOptions($this->user);
|
||||
$this->record = $model->getRecord($this->user);
|
||||
$this->title = $model->getPageTitle();
|
||||
$this->isAdmin = $app->isClient('administrator');
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
// Backup codes are a special case, rendered with a special layout
|
||||
if ($this->record->method == 'backupcodes') {
|
||||
$this->setLayout('backupcodes');
|
||||
|
||||
$backupCodes = $this->record->options;
|
||||
|
||||
if (!\is_array($backupCodes)) {
|
||||
$backupCodes = [];
|
||||
}
|
||||
|
||||
$backupCodes = array_filter(
|
||||
$backupCodes,
|
||||
function ($x) {
|
||||
return !empty($x);
|
||||
}
|
||||
);
|
||||
|
||||
if (\count($backupCodes) % 2 != 0) {
|
||||
$backupCodes[] = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The call to array_merge resets the array indices. This is necessary since array_filter kept the indices,
|
||||
* meaning our elements are completely out of order.
|
||||
*/
|
||||
$this->backupCodes = array_merge($backupCodes);
|
||||
}
|
||||
|
||||
// Set up the isEditExisting property.
|
||||
$this->isEditExisting = !empty($this->record->id);
|
||||
|
||||
// Back-end: always show a title in the 'title' module position, not in the page body
|
||||
if ($this->isAdmin) {
|
||||
ToolbarHelper::title($this->title, 'users user-lock');
|
||||
|
||||
$helpUrl = $this->renderOptions['help_url'];
|
||||
|
||||
if (!empty($helpUrl)) {
|
||||
$toolbar->help('', false, $helpUrl);
|
||||
}
|
||||
|
||||
$this->title = '';
|
||||
}
|
||||
|
||||
$returnUrl = empty($this->returnURL) ? '' : base64_decode($this->returnURL);
|
||||
$returnUrl = ($returnUrl && Uri::isInternal($returnUrl))
|
||||
? $returnUrl
|
||||
: Route::_('index.php?option=com_users&task=methods.display&user_id=' . $this->user->id);
|
||||
|
||||
if ($this->isAdmin && $this->getLayout() === 'edit') {
|
||||
$button = (new BasicButton('user-mfa-edit-save'))
|
||||
->text($this->renderOptions['submit_text'])
|
||||
->icon($this->renderOptions['submit_icon'])
|
||||
->onclick('document.getElementById(\'user-mfa-edit-save\').click()');
|
||||
|
||||
if ($this->renderOptions['show_submit'] || $this->isEditExisting) {
|
||||
$toolbar->appendButton($button);
|
||||
}
|
||||
|
||||
$button = (new LinkButton('user-mfa-edit-cancel'))
|
||||
->url($returnUrl)
|
||||
->text('JCANCEL')
|
||||
->buttonClass('btn btn-danger')
|
||||
->icon('icon-cancel-2');
|
||||
$toolbar->appendButton($button);
|
||||
} elseif ($this->isAdmin && $this->getLayout() === 'backupcodes') {
|
||||
$arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
|
||||
$button = (new LinkButton('user-mfa-edit-cancel'))
|
||||
->url($returnUrl)
|
||||
->text('JTOOLBAR_BACK')
|
||||
->icon('icon-' . $arrow);
|
||||
$toolbar->appendButton($button);
|
||||
|
||||
$button = (new LinkButton('user-mfa-edit-cancel'))
|
||||
->url(
|
||||
Route::_(
|
||||
sprintf(
|
||||
"index.php?option=com_users&task=method.regenerateBackupCodes&user_id=%s&%s=1&returnurl=%s",
|
||||
$this->user->id,
|
||||
Factory::getApplication()->getFormToken(),
|
||||
base64_encode($returnUrl)
|
||||
)
|
||||
)
|
||||
)
|
||||
->text('COM_USERS_MFA_BACKUPCODES_RESET')
|
||||
->buttonClass('btn btn-danger')
|
||||
->icon('icon-refresh');
|
||||
$toolbar->appendButton($button);
|
||||
}
|
||||
|
||||
// Display the view
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
196
administrator/components/com_users/src/View/Methods/HtmlView.php
Normal file
196
administrator/components/com_users/src/View/Methods/HtmlView.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Methods;
|
||||
|
||||
use Joomla\CMS\Event\MultiFactor\NotifyActionLog;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Component\Users\Administrator\DataShape\MethodDescriptor;
|
||||
use Joomla\Component\Users\Administrator\Model\BackupcodesModel;
|
||||
use Joomla\Component\Users\Administrator\Model\MethodsModel;
|
||||
use Joomla\Component\Users\Administrator\View\SiteTemplateTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View for Multi-factor Authentication methods list page
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
use SiteTemplateTrait;
|
||||
|
||||
/**
|
||||
* Is this an administrator page?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $isAdmin = false;
|
||||
|
||||
/**
|
||||
* The MFA Methods available for this user
|
||||
*
|
||||
* @var array
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $methods = [];
|
||||
|
||||
/**
|
||||
* The return URL to use for all links and forms
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $returnURL = null;
|
||||
|
||||
/**
|
||||
* Are there any active MFA Methods at all?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $mfaActive = false;
|
||||
|
||||
/**
|
||||
* Which Method has the default record?
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $defaultMethod = '';
|
||||
|
||||
/**
|
||||
* The user object used to display this page
|
||||
*
|
||||
* @var User
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $user = null;
|
||||
|
||||
/**
|
||||
* Is this page part of the mandatory Multi-factor Authentication setup?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public $isMandatoryMFASetup = false;
|
||||
|
||||
/**
|
||||
* Execute and display a template script.
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \JViewLegacy::loadTemplate()
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->setSiteTemplateStyle();
|
||||
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if (empty($this->user)) {
|
||||
$this->user = $this->getCurrentUser();
|
||||
}
|
||||
|
||||
/** @var MethodsModel $model */
|
||||
$model = $this->getModel();
|
||||
|
||||
if ($this->getLayout() !== 'firsttime') {
|
||||
$this->setLayout('default');
|
||||
}
|
||||
|
||||
$this->methods = $model->getMethods($this->user);
|
||||
$this->isAdmin = $app->isClient('administrator');
|
||||
$activeRecords = 0;
|
||||
|
||||
foreach ($this->methods as $methodName => $method) {
|
||||
$methodActiveRecords = \count($method['active']);
|
||||
|
||||
if (!$methodActiveRecords) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$activeRecords += $methodActiveRecords;
|
||||
$this->mfaActive = true;
|
||||
|
||||
foreach ($method['active'] as $record) {
|
||||
if ($record->default) {
|
||||
$this->defaultMethod = $methodName;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no backup codes yet we should create new ones
|
||||
/** @var BackupcodesModel $model */
|
||||
$model = $this->getModel('backupcodes');
|
||||
$backupCodes = $model->getBackupCodes($this->user);
|
||||
|
||||
if ($activeRecords && empty($backupCodes)) {
|
||||
$model->regenerateBackupCodes($this->user);
|
||||
}
|
||||
|
||||
$backupCodesRecord = $model->getBackupCodesRecord($this->user);
|
||||
|
||||
if (!\is_null($backupCodesRecord)) {
|
||||
$this->methods = array_merge(
|
||||
[
|
||||
'backupcodes' => new MethodDescriptor(
|
||||
[
|
||||
'name' => 'backupcodes',
|
||||
'display' => Text::_('COM_USERS_USER_BACKUPCODES'),
|
||||
'shortinfo' => Text::_('COM_USERS_USER_BACKUPCODES_DESC'),
|
||||
'image' => 'media/com_users/images/emergency.svg',
|
||||
'canDisable' => false,
|
||||
'active' => [$backupCodesRecord],
|
||||
]
|
||||
),
|
||||
],
|
||||
$this->methods
|
||||
);
|
||||
}
|
||||
|
||||
$this->isMandatoryMFASetup = $activeRecords === 0 && $app->getSession()->get('com_users.mandatory_mfa_setup', 0) === 1;
|
||||
|
||||
// Back-end: always show a title in the 'title' module position, not in the page body
|
||||
if ($this->isAdmin) {
|
||||
ToolbarHelper::title(Text::_('COM_USERS_MFA_LIST_PAGE_HEAD'), 'users user-lock');
|
||||
|
||||
if ($this->getCurrentUser()->authorise('core.manage', 'com_users')) {
|
||||
$toolbar = Toolbar::getInstance();
|
||||
$arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
|
||||
$toolbar->link('JTOOLBAR_BACK', 'index.php?option=com_users')
|
||||
->icon('icon-' . $arrow);
|
||||
}
|
||||
}
|
||||
|
||||
// Display the view
|
||||
parent::display($tpl);
|
||||
|
||||
$event = new NotifyActionLog('onComUsersViewMethodsAfterDisplay', [$this]);
|
||||
Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
|
||||
|
||||
Text::script('JGLOBAL_CONFIRM_DELETE');
|
||||
}
|
||||
}
|
||||
145
administrator/components/com_users/src/View/Note/HtmlView.php
Normal file
145
administrator/components/com_users/src/View/Note/HtmlView.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Note;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User note edit view
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The edit form.
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Override the display method for the view.
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
// Initialise view variables.
|
||||
$this->state = $this->get('State');
|
||||
$this->item = $this->get('Item');
|
||||
$this->form = $this->get('Form');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
parent::display($tpl);
|
||||
$this->addToolbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$input = Factory::getApplication()->getInput();
|
||||
$input->set('hidemainmenu', 1);
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$isNew = ($this->item->id == 0);
|
||||
$checkedOut = !(\is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
// Since we don't track these assets at the item level, use the category id.
|
||||
$canDo = ContentHelper::getActions('com_users', 'category', $this->item->catid);
|
||||
|
||||
ToolbarHelper::title(Text::_('COM_USERS_NOTES'), 'users user');
|
||||
|
||||
// If not checked out, can save the item.
|
||||
if (!$checkedOut && ($canDo->get('core.edit') || \count($user->getAuthorisedCategories('com_users', 'core.create')))) {
|
||||
$toolbar->apply('note.apply');
|
||||
}
|
||||
|
||||
$saveGroup = $toolbar->dropdownButton('save-group');
|
||||
|
||||
$saveGroup->configure(
|
||||
function (Toolbar $childBar) use ($checkedOut, $canDo, $user, $isNew) {
|
||||
// If not checked out, can save the item.
|
||||
if (!$checkedOut && ($canDo->get('core.edit') || \count($user->getAuthorisedCategories('com_users', 'core.create')))) {
|
||||
$childBar->save('note.save');
|
||||
}
|
||||
|
||||
if (!$checkedOut && \count($user->getAuthorisedCategories('com_users', 'core.create'))) {
|
||||
$childBar->save2new('note.save2new');
|
||||
}
|
||||
|
||||
// If an existing item, can save to a copy.
|
||||
if (!$isNew && (\count($user->getAuthorisedCategories('com_users', 'core.create')) > 0)) {
|
||||
$childBar->save2copy('note.save2copy');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (empty($this->item->id)) {
|
||||
$toolbar->cancel('note.cancel', 'JTOOLBAR_CANCEL');
|
||||
} else {
|
||||
$toolbar->cancel('note.cancel');
|
||||
|
||||
if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit')) {
|
||||
$toolbar->versions('com_users.note', $this->item->id);
|
||||
}
|
||||
}
|
||||
|
||||
$toolbar->divider();
|
||||
$toolbar->help('User_Notes:_New_or_Edit');
|
||||
}
|
||||
}
|
||||
180
administrator/components/com_users/src/View/Notes/HtmlView.php
Normal file
180
administrator/components/com_users/src/View/Notes/HtmlView.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Notes;
|
||||
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Button\DropdownButton;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User notes list view
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* A list of user note objects.
|
||||
*
|
||||
* @var array
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* The pagination object.
|
||||
*
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var User
|
||||
* @since 2.5
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* Form object for search filters
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* The active search filters
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* Is this view an Empty State
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $isEmptyState = false;
|
||||
|
||||
/**
|
||||
* Override the display method for the view.
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
// Initialise view variables.
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->user = $this->get('User');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
|
||||
if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
|
||||
$this->setLayout('emptystate');
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
// Turn parameters into registry objects
|
||||
foreach ($this->items as $item) {
|
||||
$item->cparams = new Registry($item->category_params);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$canDo = ContentHelper::getActions('com_users', 'category', $this->state->get('filter.category_id'));
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(Text::_('COM_USERS_VIEW_NOTES_TITLE'), 'users user');
|
||||
|
||||
if ($canDo->get('core.create')) {
|
||||
$toolbar->addNew('note.add');
|
||||
}
|
||||
|
||||
if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin'))) {
|
||||
/** @var DropdownButton $dropdown */
|
||||
$dropdown = $toolbar->dropdownButton('status-group', 'JTOOLBAR_CHANGE_STATUS')
|
||||
->toggleSplit(false)
|
||||
->icon('icon-ellipsis-h')
|
||||
->buttonClass('btn btn-action')
|
||||
->listCheck(true);
|
||||
|
||||
$childBar = $dropdown->getChildToolbar();
|
||||
|
||||
if ($canDo->get('core.edit.state')) {
|
||||
$childBar->publish('notes.publish')->listCheck(true);
|
||||
$childBar->unpublish('notes.unpublish')->listCheck(true);
|
||||
$childBar->archive('notes.archive')->listCheck(true);
|
||||
$childBar->checkin('notes.checkin')->listCheck(true);
|
||||
}
|
||||
|
||||
if ($this->state->get('filter.published') != -2 && $canDo->get('core.edit.state')) {
|
||||
$childBar->trash('notes.trash');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) {
|
||||
$toolbar->delete('notes.delete', 'JTOOLBAR_EMPTY_TRASH')
|
||||
->message('JGLOBAL_CONFIRM_DELETE')
|
||||
->listCheck(true);
|
||||
}
|
||||
|
||||
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
|
||||
$toolbar->preferences('com_users');
|
||||
}
|
||||
|
||||
$toolbar->help('User_Notes');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Dynamically modify the frontend template when showing a MFA captive page.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
trait SiteTemplateTrait
|
||||
{
|
||||
/**
|
||||
* Set a specific site template style in the frontend application
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private function setSiteTemplateStyle(): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$templateStyle = (int) ComponentHelper::getParams('com_users')->get('captive_template', '');
|
||||
|
||||
if (empty($templateStyle) || !$app->isClient('site')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$itemId = $app->getInput()->get('Itemid');
|
||||
|
||||
if (!empty($itemId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$app->getInput()->set('templateStyle', $templateStyle);
|
||||
|
||||
try {
|
||||
$refApp = new \ReflectionObject($app);
|
||||
$refTemplate = $refApp->getProperty('template');
|
||||
$refTemplate->setAccessible(true);
|
||||
$refTemplate->setValue($app, null);
|
||||
} catch (\ReflectionException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = $app->getTemplate(true);
|
||||
|
||||
$app->set('theme', $template->template);
|
||||
$app->set('themeParams', $template->params);
|
||||
}
|
||||
}
|
||||
188
administrator/components/com_users/src/View/User/HtmlView.php
Normal file
188
administrator/components/com_users/src/View/User/HtmlView.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\User;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\CMS\User\UserFactoryAwareInterface;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User view class.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView implements UserFactoryAwareInterface
|
||||
{
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* The Form object
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* The active item
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* Gets the available groups
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $grouplist;
|
||||
|
||||
/**
|
||||
* The groups this user is assigned to
|
||||
*
|
||||
* @var array
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $groups;
|
||||
|
||||
/**
|
||||
* The model state
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The Multi-factor Authentication configuration interface for the user.
|
||||
*
|
||||
* @var string|null
|
||||
* @since 4.2.0
|
||||
*/
|
||||
protected $mfaConfigurationUI;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
// If no item found, dont show the edit screen, redirect with message
|
||||
if (false === $this->item = $this->get('Item')) {
|
||||
$app = Factory::getApplication();
|
||||
$app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_NOT_EXIST'), 'error');
|
||||
$app->redirect('index.php?option=com_users&view=users');
|
||||
}
|
||||
|
||||
$this->form = $this->get('Form');
|
||||
$this->state = $this->get('State');
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
// Prevent user from modifying own group(s)
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
if ((int) $user->id != (int) $this->item->id || $user->authorise('core.admin')) {
|
||||
$this->grouplist = $this->get('Groups');
|
||||
$this->groups = $this->get('AssignedGroups');
|
||||
}
|
||||
|
||||
$this->form->setValue('password', null);
|
||||
$this->form->setValue('password2', null);
|
||||
|
||||
$userBeingEdited = $this->getUserFactory()->loadUserById($this->item->id);
|
||||
|
||||
if ($this->item->id > 0 && (int) $userBeingEdited->id == (int) $this->item->id) {
|
||||
try {
|
||||
$this->mfaConfigurationUI = Mfa::canShowConfigurationInterface($userBeingEdited)
|
||||
? Mfa::getConfigurationInterface($userBeingEdited)
|
||||
: '';
|
||||
} catch (\Exception $e) {
|
||||
// In case something goes really wrong with the plugins; prevents hard breaks.
|
||||
$this->mfaConfigurationUI = null;
|
||||
}
|
||||
}
|
||||
|
||||
parent::display($tpl);
|
||||
|
||||
$this->addToolbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
Factory::getApplication()->getInput()->set('hidemainmenu', true);
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$canDo = ContentHelper::getActions('com_users');
|
||||
$isNew = ($this->item->id == 0);
|
||||
$isProfile = $this->item->id == $user->id;
|
||||
$toolbar = Toolbar::getInstance();
|
||||
|
||||
ToolbarHelper::title(
|
||||
Text::_(
|
||||
$isNew ? 'COM_USERS_VIEW_NEW_USER_TITLE' : ($isProfile ? 'COM_USERS_VIEW_EDIT_PROFILE_TITLE' : 'COM_USERS_VIEW_EDIT_USER_TITLE')
|
||||
),
|
||||
'user ' . ($isNew ? 'user-add' : ($isProfile ? 'user-profile' : 'user-edit'))
|
||||
);
|
||||
|
||||
if ($canDo->get('core.edit') || $canDo->get('core.create') || $isProfile) {
|
||||
$toolbar->apply('user.apply');
|
||||
}
|
||||
|
||||
$saveGroup = $toolbar->dropdownButton('save-group');
|
||||
|
||||
$saveGroup->configure(
|
||||
function (Toolbar $childBar) use ($canDo, $isProfile) {
|
||||
if ($canDo->get('core.edit') || $canDo->get('core.create') || $isProfile) {
|
||||
$childBar->save('user.save');
|
||||
}
|
||||
|
||||
if ($canDo->get('core.create') && $canDo->get('core.manage')) {
|
||||
$childBar->save2new('user.save2new');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (empty($this->item->id)) {
|
||||
$toolbar->cancel('user.cancel', 'JTOOLBAR_CANCEL');
|
||||
} else {
|
||||
$toolbar->cancel('user.cancel');
|
||||
}
|
||||
|
||||
$toolbar->divider();
|
||||
$toolbar->help('Users:_Edit_Profile');
|
||||
}
|
||||
}
|
||||
181
administrator/components/com_users/src/View/Users/HtmlView.php
Normal file
181
administrator/components/com_users/src/View/Users/HtmlView.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\Users\Administrator\View\Users;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\ContentHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\GenericDataException;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\Button\DropdownButton;
|
||||
use Joomla\CMS\Toolbar\Toolbar;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\Database\DatabaseDriver;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* View class for a list of users.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The item data.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* The pagination object.
|
||||
*
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* The model state.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.6
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* A Form instance with filter fields.
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* An array with active filters.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* An ACL object to verify user rights.
|
||||
*
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 3.6.3
|
||||
*/
|
||||
protected $canDo;
|
||||
|
||||
/**
|
||||
* An instance of DatabaseDriver.
|
||||
*
|
||||
* @var DatabaseDriver
|
||||
* @since 3.6.3
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Will be removed without replacement use database from the container instead
|
||||
* Example: Factory::getContainer()->get(DatabaseInterface::class);
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Display the view
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
$this->canDo = ContentHelper::getActions('com_users');
|
||||
$this->db = Factory::getDbo();
|
||||
|
||||
// Check for errors.
|
||||
if (\count($errors = $this->get('Errors'))) {
|
||||
throw new GenericDataException(implode("\n", $errors), 500);
|
||||
}
|
||||
|
||||
$this->addToolbar();
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
protected function addToolbar()
|
||||
{
|
||||
$canDo = $this->canDo;
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
// Get the toolbar object instance
|
||||
$toolbar = Toolbar::getInstance('toolbar');
|
||||
|
||||
ToolbarHelper::title(Text::_('COM_USERS_VIEW_USERS_TITLE'), 'users user');
|
||||
|
||||
if ($canDo->get('core.create')) {
|
||||
$toolbar->addNew('user.add');
|
||||
}
|
||||
|
||||
if ($canDo->get('core.edit.state') || $canDo->get('core.admin')) {
|
||||
/** @var DropdownButton $dropdown */
|
||||
$dropdown = $toolbar->dropdownButton('status-group', 'JTOOLBAR_CHANGE_STATUS')
|
||||
->toggleSplit(false)
|
||||
->icon('icon-ellipsis-h')
|
||||
->buttonClass('btn btn-action')
|
||||
->listCheck(true);
|
||||
|
||||
$childBar = $dropdown->getChildToolbar();
|
||||
|
||||
$childBar->publish('users.activate', 'COM_USERS_TOOLBAR_ACTIVATE');
|
||||
$childBar->unpublish('users.block', 'COM_USERS_TOOLBAR_BLOCK');
|
||||
$childBar->standardButton('unblock', 'COM_USERS_TOOLBAR_UNBLOCK', 'users.unblock')
|
||||
->listCheck(true);
|
||||
|
||||
// Add a batch button
|
||||
if (
|
||||
$user->authorise('core.create', 'com_users')
|
||||
&& $user->authorise('core.edit', 'com_users')
|
||||
&& $user->authorise('core.edit.state', 'com_users')
|
||||
) {
|
||||
$childBar->popupButton('batch', 'JTOOLBAR_BATCH')
|
||||
->selector('collapseModal')
|
||||
->listCheck(true);
|
||||
}
|
||||
|
||||
if ($canDo->get('core.delete')) {
|
||||
$childBar->delete('users.delete', 'JTOOLBAR_DELETE')
|
||||
->message('JGLOBAL_CONFIRM_DELETE')
|
||||
->listCheck(true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
|
||||
$toolbar->preferences('com_users');
|
||||
}
|
||||
|
||||
$toolbar->help('Users');
|
||||
}
|
||||
}
|
||||
134
administrator/components/com_users/tmpl/captive/default.php
Normal file
134
administrator/components/com_users/tmpl/captive/default.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Users\Administrator\Model\CaptiveModel;
|
||||
use Joomla\Component\Users\Administrator\View\Captive\HtmlView;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
/**
|
||||
* @var HtmlView $this View object
|
||||
* @var CaptiveModel $model The model
|
||||
*/
|
||||
$model = $this->getModel();
|
||||
|
||||
$this->document->getWebAssetManager()
|
||||
->useScript('com_users.two-factor-focus');
|
||||
|
||||
?>
|
||||
<div class="users-mfa-captive card card-body">
|
||||
<h2 id="users-mfa-title">
|
||||
<?php if (!empty($this->title)) : ?>
|
||||
<?php echo $this->title ?> <small> –
|
||||
<?php endif; ?>
|
||||
<?php if (!$this->allowEntryBatching) : ?>
|
||||
<?php echo $this->escape($this->record->title) ?>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($this->getModel()->translateMethodName($this->record->method)) ?>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($this->title)) : ?>
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($this->renderOptions['help_url'])) : ?>
|
||||
<span class="float-end">
|
||||
<a href="<?php echo $this->renderOptions['help_url'] ?>"
|
||||
class="btn btn-sm btn-secondary"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="icon icon-question-sign" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('JHELP') ?></span>
|
||||
</a>
|
||||
</span>
|
||||
<?php endif;?>
|
||||
</h2>
|
||||
|
||||
<?php if ($this->renderOptions['pre_message']) : ?>
|
||||
<div class="users-mfa-captive-pre-message text-muted">
|
||||
<?php echo $this->renderOptions['pre_message'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&task=captive.validate&record_id=' . ((int) $this->record->id)) ?>"
|
||||
id="users-mfa-captive-form"
|
||||
method="post"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<?php echo HTMLHelper::_('form.token') ?>
|
||||
|
||||
<div id="users-mfa-captive-form-method-fields" class="container">
|
||||
<?php if ($this->renderOptions['field_type'] == 'custom') : ?>
|
||||
<?php echo $this->renderOptions['html']; ?>
|
||||
<?php endif; ?>
|
||||
<div class="row mb-3 <?php echo $this->renderOptions['input_type'] === 'hidden' ? 'd-none' : '' ?>">
|
||||
<?php if ($this->renderOptions['label']) : ?>
|
||||
<label for="users-mfa-code" class="col-sm-3 col-form-label">
|
||||
<?php echo $this->renderOptions['label'] ?>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$attributes = array_merge(
|
||||
[
|
||||
'type' => $this->renderOptions['input_type'],
|
||||
'name' => 'code',
|
||||
'value' => '',
|
||||
'placeholder' => $this->renderOptions['placeholder'] ?? null,
|
||||
'id' => 'users-mfa-code',
|
||||
'class' => 'form-control',
|
||||
'autocomplete' => $this->renderOptions['autocomplete'] ?? 'one-time-code'
|
||||
],
|
||||
$this->renderOptions['input_attributes']
|
||||
);
|
||||
|
||||
if (strpos($attributes['class'], 'form-control') === false) {
|
||||
$attributes['class'] .= ' form-control';
|
||||
}
|
||||
?>
|
||||
<input <?php echo ArrayHelper::toString($attributes) ?>>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="users-mfa-captive-form-standard-buttons" class="row my-3 d-sm-none">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<button class="btn btn-primary me-3 <?php echo $this->renderOptions['submit_class'] ?>"
|
||||
id="users-mfa-captive-button-submit"
|
||||
style="<?php echo $this->renderOptions['hide_submit'] ? 'display: none' : '' ?>"
|
||||
type="submit">
|
||||
<span class="<?php echo $this->renderOptions['submit_icon'] ?>" aria-hidden="true"></span>
|
||||
<?php echo Text::_($this->renderOptions['submit_text']); ?>
|
||||
</button>
|
||||
|
||||
<a href="<?php echo Route::_('index.php?option=com_login&task=logout&' . Factory::getApplication()->getFormToken() . '=1') ?>"
|
||||
class="btn btn-danger btn-sm"
|
||||
id="users-mfa-captive-button-logout">
|
||||
<span class="icon icon-lock" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_MFA_LOGOUT'); ?>
|
||||
</a>
|
||||
|
||||
<?php if (count($this->records) > 1) : ?>
|
||||
<a id="users-mfa-captive-form-choose-another"
|
||||
class="btn btn-link"
|
||||
href="<?php echo Route::_('index.php?option=com_users&view=captive&task=select') ?>">
|
||||
<?php echo Text::_('COM_USERS_MFA_USE_DIFFERENT_METHOD'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($this->renderOptions['post_message']) : ?>
|
||||
<div class="users-mfa-captive-post-message">
|
||||
<?php echo $this->renderOptions['post_message'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
78
administrator/components/com_users/tmpl/captive/select.php
Normal file
78
administrator/components/com_users/tmpl/captive/select.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Component\Users\Administrator\View\Captive\HtmlView;
|
||||
|
||||
/** @var HtmlView $this */
|
||||
|
||||
$shownMethods = [];
|
||||
|
||||
?>
|
||||
<div id="com-users-select">
|
||||
<h2 id="com-users-select-heading">
|
||||
<?php echo Text::_('COM_USERS_MFA_SELECT_PAGE_HEAD'); ?>
|
||||
</h2>
|
||||
<div id="com-users-select-information">
|
||||
<p>
|
||||
<?php echo Text::_('COM_USERS_LBL_SELECT_INSTRUCTIONS'); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="com-users-select-methods p-2">
|
||||
<?php foreach ($this->records as $record) :
|
||||
if (!array_key_exists($record->method, $this->mfaMethods) && ($record->method != 'backupcodes')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$allowEntryBatching = isset($this->mfaMethods[$record->method]) ? $this->mfaMethods[$record->method]['allowEntryBatching'] : false;
|
||||
|
||||
if ($this->allowEntryBatching) {
|
||||
if ($allowEntryBatching && in_array($record->method, $shownMethods)) {
|
||||
continue;
|
||||
}
|
||||
$shownMethods[] = $record->method;
|
||||
}
|
||||
|
||||
$methodName = $this->getModel()->translateMethodName($record->method);
|
||||
?>
|
||||
<a class="com-users-method p-2 border-top border-dark bg-light d-flex flex-row flex-wrap justify-content-start align-items-center text-decoration-none gap-2 text-body"
|
||||
href="<?php echo Route::_('index.php?option=com_users&view=captive&record_id=' . $record->id)?>">
|
||||
<img src="<?php echo Uri::root() . $this->getModel()->getMethodImage($record->method) ?>"
|
||||
alt="<?php echo $this->escape(strip_tags($record->title)) ?>"
|
||||
class="com-users-method-image img-fluid" />
|
||||
<?php if (!$this->allowEntryBatching || !$allowEntryBatching) : ?>
|
||||
<span class="com-users-method-title flex-grow-1 fs-5 fw-bold">
|
||||
<?php if ($record->method === 'backupcodes') : ?>
|
||||
<?php echo $record->title ?>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($record->title) ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<small class="com-users-method-name text-muted">
|
||||
<?php echo $methodName ?>
|
||||
</small>
|
||||
<?php else : ?>
|
||||
<span class="com-users-method-title flex-grow-1 fs-5 fw-bold">
|
||||
<?php echo $methodName ?>
|
||||
</span>
|
||||
<small class="com-users-method-name text-muted">
|
||||
<?php echo $methodName ?>
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
158
administrator/components/com_users/tmpl/debuggroup/default.php
Normal file
158
administrator/components/com_users/tmpl/debuggroup/default.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$listOrder = $this->escape($this->state->get('list.ordering'));
|
||||
$listDirn = $this->escape($this->state->get('list.direction'));
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('table.columns');
|
||||
|
||||
?>
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&view=debuggroup&group_id=' . (int) $this->state->get('group_id')); ?>" method="post" name="adminForm" id="adminForm">
|
||||
<div id="j-main-container" class="j-main-container">
|
||||
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
|
||||
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
// Split the actions table
|
||||
foreach ($this->actions as $action) :
|
||||
$name = $action[0];
|
||||
if (in_array($name, ['core.login.site', 'core.login.admin', 'core.login.offline', 'core.login.api', 'core.admin'])) :
|
||||
$loginActions[] = $action;
|
||||
else :
|
||||
$actions[] = $action;
|
||||
endif;
|
||||
endforeach;
|
||||
?>
|
||||
<div class="d-flex flex-wrap">
|
||||
<?php foreach ($loginActions as $action) :
|
||||
$name = $action[0];
|
||||
$check = $this->items[0]->checks[$name];
|
||||
if ($check === true) :
|
||||
$class = 'text-success icon-check';
|
||||
$button = 'btn-success';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_ALLOW');
|
||||
elseif ($check === false) :
|
||||
$class = 'text-danger icon-times';
|
||||
$button = 'btn-danger';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_DENY');
|
||||
elseif ($check === null) :
|
||||
$class = 'text-danger icon-minus-circle';
|
||||
$button = 'btn-warning';
|
||||
$text = Text::_('COM_USERS_DEBUG_IMPLICIT_DENY');
|
||||
endif; ?>
|
||||
<div class="d-inline p-2">
|
||||
<?php echo Text::_($action[1]); ?>
|
||||
<span class="<?php echo $class; ?>" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo $text; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<table class="table">
|
||||
<caption class="visually-hidden">
|
||||
<?php echo Text::_('COM_USERS_DEBUG_GROUP_TABLE_CAPTION'); ?>,
|
||||
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
|
||||
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_ASSET_TITLE', 'a.title', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_ASSET_NAME', 'a.name', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<?php foreach ($actions as $key => $action) : ?>
|
||||
<th scope="col" class="w-6 text-center">
|
||||
<?php echo Text::_($action[1]); ?>
|
||||
</th>
|
||||
<?php endforeach; ?>
|
||||
<th scope="col" class="w-6">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_LFT', 'a.lft', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-3">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->items as $i => $item) : ?>
|
||||
<tr class="row0">
|
||||
<th scope="row">
|
||||
<?php echo $this->escape(Text::_($item->title)); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo LayoutHelper::render('joomla.html.treeprefix', ['level' => $item->level + 1]) . $this->escape($item->name); ?>
|
||||
</td>
|
||||
<?php foreach ($actions as $action) : ?>
|
||||
<?php
|
||||
$name = $action[0];
|
||||
$check = $item->checks[$name];
|
||||
if ($check === true) :
|
||||
$class = 'text-success icon-check';
|
||||
$button = 'btn-success';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_ALLOW');
|
||||
elseif ($check === false) :
|
||||
$class = 'text-danger icon-times';
|
||||
$button = 'btn-danger';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_DENY');
|
||||
elseif ($check === null) :
|
||||
$class = 'text-danger icon-minus-circle';
|
||||
$button = 'btn-warning';
|
||||
$text = Text::_('COM_USERS_DEBUG_IMPLICIT_DENY');
|
||||
else :
|
||||
$class = '';
|
||||
$button = '';
|
||||
$text = '';
|
||||
endif;
|
||||
?>
|
||||
<td class="text-center">
|
||||
<span class="<?php echo $class; ?>" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"> <?php echo $text; ?></span>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
<td>
|
||||
<?php echo (int) $item->lft; ?>
|
||||
- <?php echo (int) $item->rgt; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo (int) $item->id; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="legend">
|
||||
<span class="text-danger icon-minus-circle" aria-hidden="true"></span> <?php echo Text::_('COM_USERS_DEBUG_IMPLICIT_DENY'); ?>
|
||||
<span class="text-success icon-check" aria-hidden="true"></span> <?php echo Text::_('COM_USERS_DEBUG_EXPLICIT_ALLOW'); ?>
|
||||
<span class="text-danger icon-times" aria-hidden="true"></span> <?php echo Text::_('COM_USERS_DEBUG_EXPLICIT_DENY'); ?>
|
||||
</div>
|
||||
|
||||
<?php // load the pagination. ?>
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<input type="hidden" name="boxchecked" value="0">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
164
administrator/components/com_users/tmpl/debuguser/default.php
Normal file
164
administrator/components/com_users/tmpl/debuguser/default.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$listOrder = $this->escape($this->state->get('list.ordering'));
|
||||
$listDirn = $this->escape($this->state->get('list.direction'));
|
||||
|
||||
$loginActions = [];
|
||||
$actions = [];
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('table.columns');
|
||||
|
||||
?>
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&view=debuguser&user_id=' . (int) $this->state->get('user_id')); ?>" method="post" name="adminForm" id="adminForm">
|
||||
<div id="j-main-container" class="j-main-container">
|
||||
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
|
||||
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
// Split the actions table
|
||||
foreach ($this->actions as $action) :
|
||||
$name = $action[0];
|
||||
if (in_array($name, ['core.login.site', 'core.login.admin', 'core.login.api', 'core.login.offline'])) :
|
||||
$loginActions[] = $action;
|
||||
else :
|
||||
$actions[] = $action;
|
||||
endif;
|
||||
endforeach;
|
||||
?>
|
||||
<div class="d-flex flex-wrap">
|
||||
<?php foreach ($loginActions as $action) :
|
||||
$name = $action[0];
|
||||
$check = $this->items[0]->checks[$name];
|
||||
if ($check === true) :
|
||||
$class = 'text-success icon-check';
|
||||
$button = 'btn-success';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_ALLOW');
|
||||
elseif ($check === false) :
|
||||
$class = 'text-danger icon-times';
|
||||
$button = 'btn-danger';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_DENY');
|
||||
elseif ($check === null) :
|
||||
$class = 'text-danger icon-minus-circle';
|
||||
$button = 'btn-warning';
|
||||
$text = Text::_('COM_USERS_DEBUG_IMPLICIT_DENY');
|
||||
endif;
|
||||
?>
|
||||
<div class="d-inline p-2">
|
||||
<?php echo Text::_($action[1]); ?>
|
||||
<span class="<?php echo $class; ?>" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_($text); ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<caption class="visually-hidden">
|
||||
<?php echo Text::_('COM_USERS_DEBUG_USER_TABLE_CAPTION'); ?>,
|
||||
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
|
||||
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_ASSET_TITLE', 'a.title', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_ASSET_NAME', 'a.name', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<?php foreach ($actions as $key => $action) : ?>
|
||||
<th scope="col" class="w-6 text-center">
|
||||
<?php echo Text::_($action[1]); ?>
|
||||
</th>
|
||||
<?php endforeach; ?>
|
||||
<th scope="col" class="w-6">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_LFT', 'a.lft', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-3">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->items as $i => $item) :?>
|
||||
<tr class="row0" scope="row">
|
||||
<td>
|
||||
<?php echo $this->escape(Text::_($item->title)); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo LayoutHelper::render('joomla.html.treeprefix', ['level' => $item->level + 1]) . $this->escape($item->name); ?>
|
||||
</td>
|
||||
<?php foreach ($actions as $action) : ?>
|
||||
<?php
|
||||
$name = $action[0];
|
||||
$check = $item->checks[$name];
|
||||
if ($check === true) :
|
||||
$class = 'text-success icon-check';
|
||||
$button = 'btn-success';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_ALLOW');
|
||||
elseif ($check === false) :
|
||||
$class = 'text-danger icon-times';
|
||||
$button = 'btn-danger';
|
||||
$text = Text::_('COM_USERS_DEBUG_EXPLICIT_DENY');
|
||||
elseif ($check === null) :
|
||||
$class = 'text-danger icon-minus-circle';
|
||||
$button = 'btn-warning';
|
||||
$text = Text::_('COM_USERS_DEBUG_IMPLICIT_DENY');
|
||||
else :
|
||||
$class = '';
|
||||
$button = '';
|
||||
$text = '';
|
||||
endif;
|
||||
?>
|
||||
<td class="text-center">
|
||||
<span class="<?php echo $class; ?>" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"> <?php echo $text; ?></span>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
<td>
|
||||
<?php echo (int) $item->lft; ?>
|
||||
- <?php echo (int) $item->rgt; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo (int) $item->id; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="legend">
|
||||
<span class="text-danger icon-minus-circle" aria-hidden="true"></span> <?php echo Text::_('COM_USERS_DEBUG_IMPLICIT_DENY'); ?>
|
||||
<span class="text-success icon-check" aria-hidden="true"></span> <?php echo Text::_('COM_USERS_DEBUG_EXPLICIT_ALLOW'); ?>
|
||||
<span class="text-danger icon-times" aria-hidden="true"> </span><?php echo Text::_('COM_USERS_DEBUG_EXPLICIT_DENY'); ?>
|
||||
</div>
|
||||
|
||||
<?php // load the pagination. ?>
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<input type="hidden" name="boxchecked" value="0">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
40
administrator/components/com_users/tmpl/group/edit.php
Normal file
40
administrator/components/com_users/tmpl/group/edit.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('keepalive')
|
||||
->useScript('form.validate');
|
||||
|
||||
$this->useCoreUI = true;
|
||||
?>
|
||||
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&layout=edit&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="group-form" aria-label="<?php echo Text::_('COM_USERS_GROUP_FORM_' . ((int) $this->item->id === 0 ? 'NEW' : 'EDIT'), true); ?>" class="main-card form-validate">
|
||||
<?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', ['active' => 'details', 'recall' => true, 'breakpoint' => 768]); ?>
|
||||
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'details', Text::_('COM_USERS_USERGROUP_DETAILS')); ?>
|
||||
<div class="form-grid">
|
||||
<?php echo $this->form->renderField('title'); ?>
|
||||
<?php echo $this->form->renderField('parent_id'); ?>
|
||||
</div>
|
||||
<?php echo HTMLHelper::_('uitab.endTab'); ?>
|
||||
<?php $this->ignore_fieldsets = ['group_details']; ?>
|
||||
<?php echo LayoutHelper::render('joomla.edit.params', $this); ?>
|
||||
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
8
administrator/components/com_users/tmpl/group/edit.xml
Normal file
8
administrator/components/com_users/tmpl/group/edit.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_USERS_GROUP_VIEW_EDIT_TITLE">
|
||||
<message>
|
||||
<![CDATA[COM_USERS_GROUP_VIEW_EDIT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
144
administrator/components/com_users/tmpl/groups/default.php
Normal file
144
administrator/components/com_users/tmpl/groups/default.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$listOrder = $this->escape($this->state->get('list.ordering'));
|
||||
$listDirn = $this->escape($this->state->get('list.direction'));
|
||||
|
||||
Text::script('COM_USERS_GROUPS_CONFIRM_DELETE', true);
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('com_users.admin-users-groups')
|
||||
->useScript('multiselect')
|
||||
->useScript('table.columns');
|
||||
|
||||
?>
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&view=groups'); ?>" method="post" name="adminForm" id="adminForm">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="j-main-container" class="j-main-container">
|
||||
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this, 'options' => ['filterButton' => false]]); ?>
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
|
||||
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<table class="table" id="groupList">
|
||||
<caption class="visually-hidden">
|
||||
<?php echo Text::_('COM_USERS_GROUPS_TABLE_CAPTION'); ?>,
|
||||
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
|
||||
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="w-1 text-center">
|
||||
<?php echo HTMLHelper::_('grid.checkall'); ?>
|
||||
</td>
|
||||
<th scope="col">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_GROUP_TITLE', 'a.title', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-10 text-center">
|
||||
<?php echo Text::_('COM_USERS_DEBUG_PERMISSIONS'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-10 text-center d-none d-md-table-cell">
|
||||
<span class="icon-check" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_COUNT_ENABLED_USERS'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-10 text-center d-none d-md-table-cell">
|
||||
<span class="icon-times" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_COUNT_DISABLED_USERS'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-5 d-none d-md-table-cell">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->items as $i => $item) :
|
||||
$canCreate = $user->authorise('core.create', 'com_users');
|
||||
$canEdit = $user->authorise('core.edit', 'com_users');
|
||||
|
||||
// If this group is super admin and this user is not super admin, $canEdit is false
|
||||
if (!$user->authorise('core.admin') && Access::checkGroup($item->id, 'core.admin')) {
|
||||
$canEdit = false;
|
||||
}
|
||||
$canChange = $user->authorise('core.edit.state', 'com_users');
|
||||
?>
|
||||
<tr class="row<?php echo $i % 2; ?>">
|
||||
<td class="text-center" data-usercount="<?php echo $item->user_count; ?>">
|
||||
<?php if ($canEdit) : ?>
|
||||
<?php echo HTMLHelper::_('grid.id', $i, $item->id, false, 'cid', 'cb', $item->title); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<th scope="row">
|
||||
<?php echo LayoutHelper::render('joomla.html.treeprefix', ['level' => $item->level + 1]); ?>
|
||||
<?php if ($canEdit) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&task=group.edit&id=' . $item->id); ?>" title="<?php echo Text::_('JACTION_EDIT'); ?> <?php echo $this->escape($item->title); ?>">
|
||||
<?php echo $this->escape($item->title); ?></a>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
<?php endif; ?>
|
||||
</th>
|
||||
<td class="text-center btns">
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=debuggroup&group_id=' . (int) $item->id); ?>">
|
||||
<span class="icon-list" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('COM_USERS_DEBUG_PERMISSIONS'); ?></span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center btns itemnumber d-none d-md-table-cell">
|
||||
<a class="btn <?php echo $item->count_enabled > 0 ? 'btn-success' : 'btn-secondary'; ?>"
|
||||
href="<?php echo Route::_('index.php?option=com_users&view=users&filter[group_id]=' . (int) $item->id . '&filter[state]=0'); ?>"
|
||||
aria-describedby="tip-enabled<?php echo $i; ?>">
|
||||
<?php echo $item->count_enabled; ?>
|
||||
</a>
|
||||
<div role="tooltip" id="tip-enabled<?php echo $i; ?>">
|
||||
<?php echo Text::_('COM_USERS_COUNT_ENABLED_USERS'); ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center btns itemnumber d-none d-md-table-cell">
|
||||
<a class="btn <?php echo $item->count_disabled > 0 ? 'btn-danger' : 'btn-secondary'; ?>"
|
||||
href="<?php echo Route::_('index.php?option=com_users&view=users&filter[group_id]=' . (int) $item->id . '&filter[state]=1'); ?>"
|
||||
aria-describedby="tip-blocked<?php echo $i; ?>">
|
||||
<?php echo $item->count_disabled; ?>
|
||||
</a>
|
||||
<div role="tooltip" id="tip-blocked<?php echo $i; ?>">
|
||||
<?php echo Text::_('COM_USERS_COUNT_DISABLED_USERS'); ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<?php echo (int) $item->id; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php // load the pagination. ?>
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<input type="hidden" name="boxchecked" value="0">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_USERS_GROUPS_VIEW_DEFAULT_TITLE">
|
||||
<message>
|
||||
<![CDATA[COM_USERS_GROUPS_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
54
administrator/components/com_users/tmpl/level/edit.php
Normal file
54
administrator/components/com_users/tmpl/level/edit.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('keepalive')
|
||||
->useScript('form.validate');
|
||||
|
||||
?>
|
||||
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="level-form" aria-label="<?php echo Text::_('COM_USERS_LEVEL_FORM_' . ((int) $this->item->id === 0 ? 'NEW' : 'EDIT'), true); ?>" class="form-validate main-card">
|
||||
<?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', ['active' => 'details', 'recall' => true, 'breakpoint' => 768]); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'details', Text::_('COM_USERS_LEVEL_DETAILS')); ?>
|
||||
<fieldset class="options-form">
|
||||
<legend><?php echo Text::_('COM_USERS_LEVEL_DETAILS'); ?></legend>
|
||||
<div class="control-group">
|
||||
<div class="control-label">
|
||||
<?php echo $this->form->getLabel('title'); ?>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<?php echo $this->form->getInput('title'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php echo HTMLHelper::_('uitab.endTab'); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'groups', Text::_('COM_USERS_USER_GROUPS_HAVING_ACCESS')); ?>
|
||||
<fieldset class="options-form">
|
||||
<legend><?php echo Text::_('COM_USERS_USER_GROUPS_HAVING_ACCESS'); ?></legend>
|
||||
<div>
|
||||
<?php echo HTMLHelper::_('access.usergroups', 'jform[rules]', $this->item->rules, true); ?>
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php echo HTMLHelper::_('uitab.endTab'); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
8
administrator/components/com_users/tmpl/level/edit.xml
Normal file
8
administrator/components/com_users/tmpl/level/edit.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_USERS_LEVEL_VIEW_EDIT_TITLE">
|
||||
<message>
|
||||
<![CDATA[COM_USERS_LEVEL_VIEW_EDIT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
143
administrator/components/com_users/tmpl/levels/default.php
Normal file
143
administrator/components/com_users/tmpl/levels/default.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Access\Access;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\Component\Users\Administrator\Helper\UsersHelper;
|
||||
|
||||
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('table.columns')
|
||||
->useScript('multiselect');
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
$listOrder = $this->escape($this->state->get('list.ordering'));
|
||||
$listDirn = $this->escape($this->state->get('list.direction'));
|
||||
$saveOrder = $listOrder == 'a.ordering';
|
||||
|
||||
if ($saveOrder && !empty($this->items)) {
|
||||
$saveOrderingUrl = 'index.php?option=com_users&task=levels.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
|
||||
HTMLHelper::_('draggablelist.draggable');
|
||||
}
|
||||
?>
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&view=levels'); ?>" method="post" id="adminForm" name="adminForm">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="j-main-container" class="j-main-container">
|
||||
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this, 'options' => ['filterButton' => false]]); ?>
|
||||
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
|
||||
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<table class="table" id="levelList">
|
||||
<caption class="visually-hidden">
|
||||
<?php echo Text::_('COM_USERS_LEVELS_TABLE_CAPTION'); ?>,
|
||||
<span id="orderedBy"><?php echo Text::_('JGLOBAL_SORTED_BY'); ?> </span>,
|
||||
<span id="filteredBy"><?php echo Text::_('JGLOBAL_FILTERED_BY'); ?></span>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="w-1 text-center">
|
||||
<?php echo HTMLHelper::_('grid.checkall'); ?>
|
||||
</td>
|
||||
<th scope="col" class="w-1 text-center d-none d-md-table-cell">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', '', 'a.ordering', $listDirn, $listOrder, null, 'asc', 'JGRID_HEADING_ORDERING', 'icon-sort'); ?>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'COM_USERS_HEADING_LEVEL_NAME', 'a.title', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">
|
||||
<?php echo Text::_('COM_USERS_USER_GROUPS_HAVING_ACCESS'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-1 d-none d-md-table-cell">
|
||||
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody<?php if ($saveOrder) :
|
||||
?> class="js-draggable" data-url="<?php echo $saveOrderingUrl; ?>" data-direction="<?php echo strtolower($listDirn); ?>"<?php
|
||||
endif; ?>>
|
||||
<?php $count = count($this->items); ?>
|
||||
<?php foreach ($this->items as $i => $item) :
|
||||
$ordering = ($listOrder == 'a.ordering');
|
||||
$canCreate = $user->authorise('core.create', 'com_users');
|
||||
$canEdit = $user->authorise('core.edit', 'com_users');
|
||||
$canChange = $user->authorise('core.edit.state', 'com_users');
|
||||
|
||||
// Decode level groups
|
||||
$groups = json_decode($item->rules);
|
||||
|
||||
// If this group is super admin and this user is not super admin, $canEdit is false
|
||||
if (!$this->getCurrentUser()->authorise('core.admin') && $groups && Access::checkGroup($groups[0], 'core.admin')) {
|
||||
$canEdit = false;
|
||||
$canChange = false;
|
||||
}
|
||||
?>
|
||||
<tr class="row<?php echo $i % 2; ?>">
|
||||
<td class="text-center">
|
||||
<?php if ($canEdit) : ?>
|
||||
<?php echo HTMLHelper::_('grid.id', $i, $item->id, false, 'cid', 'cb', $item->title); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center d-none d-md-table-cell">
|
||||
<?php
|
||||
$iconClass = '';
|
||||
if (!$canChange) {
|
||||
$iconClass = ' inactive';
|
||||
} elseif (!$saveOrder) {
|
||||
$iconClass = ' inactive" title="' . Text::_('JORDERINGDISABLED');
|
||||
}
|
||||
?>
|
||||
<span class="sortable-handler<?php echo $iconClass ?>">
|
||||
<span class="icon-ellipsis-v" aria-hidden="true"></span>
|
||||
</span>
|
||||
<?php if ($canChange && $saveOrder) : ?>
|
||||
<input type="text" name="order[]" size="5" value="<?php echo $item->ordering; ?>" class="width-20 text-area-order hidden">
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<th scope="row">
|
||||
<?php if ($canEdit) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&task=level.edit&id=' . $item->id); ?>" title="<?php echo Text::_('JACTION_EDIT'); ?> <?php echo $this->escape($item->title); ?>">
|
||||
<?php echo $this->escape($item->title); ?></a>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
<?php endif; ?>
|
||||
</th>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<?php echo UsersHelper::getVisibleByGroups($item->rules); ?>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<?php echo (int) $item->id; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php // load the pagination. ?>
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="task" value="">
|
||||
<input type="hidden" name="boxchecked" value="0">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_USERS_LEVELS_VIEW_DEFAULT_TITLE">
|
||||
<message>
|
||||
<![CDATA[COM_USERS_LEVELS_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
62
administrator/components/com_users/tmpl/mail/default.php
Normal file
62
administrator/components/com_users/tmpl/mail/default.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var Joomla\Component\Users\Administrator\View\Mail\HtmlView $this */
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useScript('keepalive')
|
||||
->useScript('form.validate');
|
||||
|
||||
$comUserParams = ComponentHelper::getParams('com_users');
|
||||
?>
|
||||
|
||||
<form action="<?php echo Route::_('index.php?option=com_users&view=mail'); ?>" name="adminForm" method="post" id="mail-form" aria-label="<?php echo Text::_('COM_USERS_MASSMAIL_FORM_NEW'); ?>" class="main-card p-4 form-validate">
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="control-group">
|
||||
<?php echo $this->form->getLabel('subject'); ?>
|
||||
<span class="input-group">
|
||||
<?php if (!empty($comUserParams->get('mailSubjectPrefix'))) : ?>
|
||||
<span class="input-group-text"><?php echo $comUserParams->get('mailSubjectPrefix'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php echo $this->form->getInput('subject'); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<?php echo $this->form->getLabel('message'); ?>
|
||||
<?php echo $this->form->getInput('message'); ?>
|
||||
<?php if (!empty($comUserParams->get('mailBodySuffix'))) : ?>
|
||||
<div class="mt-1 card">
|
||||
<div class="card-body">
|
||||
<?php echo $comUserParams->get('mailBodySuffix'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<?php echo $this->form->renderField('recurse'); ?>
|
||||
<?php echo $this->form->renderField('mode'); ?>
|
||||
<?php echo $this->form->renderField('disabled'); ?>
|
||||
<?php echo $this->form->renderField('bcc'); ?>
|
||||
<?php echo $this->form->renderField('group'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="task" value="">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
8
administrator/components/com_users/tmpl/mail/default.xml
Normal file
8
administrator/components/com_users/tmpl/mail/default.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_USERS_MAIL_VIEW_DEFAULT_TITLE">
|
||||
<message>
|
||||
<![CDATA[COM_USERS_MAIL_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Users\Administrator\View\Method\HtmlView;
|
||||
|
||||
/** @var HtmlView $this */
|
||||
|
||||
HTMLHelper::_('bootstrap.tooltip', '.hasTooltip');
|
||||
|
||||
$cancelURL = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $this->user->id);
|
||||
|
||||
if (!empty($this->returnURL)) {
|
||||
$cancelURL = $this->escape(base64_decode($this->returnURL));
|
||||
}
|
||||
|
||||
if ($this->record->method != 'backupcodes') {
|
||||
throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||
}
|
||||
|
||||
?>
|
||||
<h2>
|
||||
<?php echo Text::_('COM_USERS_USER_BACKUPCODES') ?>
|
||||
</h2>
|
||||
|
||||
<p class="text-muted">
|
||||
<?php echo Text::_('COM_USERS_USER_BACKUPCODES_DESC') ?>
|
||||
</p>
|
||||
|
||||
<table class="table table-striped">
|
||||
<?php for ($i = 0; $i < (count($this->backupCodes) / 2); $i++) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if (!empty($this->backupCodes[2 * $i])) : ?>
|
||||
<?php // This is a Key emoji; we can hide it from screen readers ?>
|
||||
<span aria-hidden="true">🔑</span>
|
||||
<?php echo $this->backupCodes[2 * $i] ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($this->backupCodes[1 + 2 * $i])) : ?>
|
||||
<?php // This is a Key emoji; we can hide it from screen readers ?>
|
||||
<span aria-hidden="true">🔑</span>
|
||||
<?php echo $this->backupCodes[1 + 2 * $i] ?>
|
||||
<?php endif ;?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endfor; ?>
|
||||
</table>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<span class="icon-info-circle" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_MFA_BACKUPCODES_RESET_INFO'); ?>
|
||||
</div>
|
||||
|
||||
<div class="d-sm-none">
|
||||
<a class="btn btn-danger" href="<?php echo Route::_(sprintf("index.php?option=com_users&task=method.regenerateBackupCodes&user_id=%s&%s=1%s", $this->user->id, Factory::getApplication()->getFormToken(), empty($this->returnURL) ? '' : '&returnurl=' . $this->returnURL)) ?>">
|
||||
<span class="icon icon-refresh" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_MFA_BACKUPCODES_RESET'); ?>
|
||||
</a>
|
||||
|
||||
<a href="<?php echo $cancelURL ?>"
|
||||
class="btn btn-secondary">
|
||||
<span class="icon icon-cancel-2 icon-ban-circle"></span>
|
||||
<?php echo Text::_('JCANCEL'); ?>
|
||||
</a>
|
||||
</div>
|
||||
185
administrator/components/com_users/tmpl/method/edit.php
Normal file
185
administrator/components/com_users/tmpl/method/edit.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Users\Administrator\View\Method\HtmlView;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
/** @var HtmlView $this */
|
||||
|
||||
$cancelURL = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $this->user->id);
|
||||
|
||||
if (!empty($this->returnURL)) {
|
||||
$cancelURL = $this->escape(base64_decode($this->returnURL));
|
||||
}
|
||||
|
||||
$recordId = (int) $this->record->id ?? 0;
|
||||
$method = $this->record->method ?? $this->getModel()->getState('method');
|
||||
$userId = (int) $this->user->id ?? 0;
|
||||
$headingLevel = 2;
|
||||
$hideSubmit = !$this->renderOptions['show_submit'] && !$this->isEditExisting
|
||||
?>
|
||||
<div class="card card-body">
|
||||
<form action="<?php echo Route::_(sprintf("index.php?option=com_users&task=method.save&id=%d&method=%s&user_id=%d", $recordId, $method, $userId)) ?>"
|
||||
class="form form-horizontal" id="com-users-method-edit" method="post">
|
||||
<?php echo HTMLHelper::_('form.token') ?>
|
||||
<?php if (!empty($this->returnURL)) : ?>
|
||||
<input type="hidden" name="returnurl" value="<?php echo $this->escape($this->returnURL) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($this->renderOptions['hidden_data'])) : ?>
|
||||
<?php foreach ($this->renderOptions['hidden_data'] as $key => $value) : ?>
|
||||
<input type="hidden" name="<?php echo $this->escape($key) ?>" value="<?php echo $this->escape($value) ?>">
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($this->title)) : ?>
|
||||
<?php if (!empty($this->renderOptions['help_url'])) : ?>
|
||||
<span class="float-end">
|
||||
<a href="<?php echo $this->renderOptions['help_url'] ?>"
|
||||
class="btn btn-sm btn-dark"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="icon icon-question-sign" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('JHELP') ?></span>
|
||||
</a>
|
||||
</span>
|
||||
<?php endif;?>
|
||||
<h<?php echo $headingLevel ?> id="com-users-method-edit-head">
|
||||
<?php echo Text::_($this->title) ?>
|
||||
</h<?php echo $headingLevel ?>>
|
||||
<?php $headingLevel++ ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<label class="col-sm-3 col-form-label"
|
||||
for="com-users-method-edit-title">
|
||||
<?php echo Text::_('COM_USERS_MFA_EDIT_FIELD_TITLE'); ?>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="com-users-method-edit-title"
|
||||
name="title"
|
||||
value="<?php echo $this->escape($this->record->title) ?>"
|
||||
aria-describedby="com-users-method-edit-help">
|
||||
<p class="form-text" id="com-users-method-edit-help">
|
||||
<?php echo $this->escape(Text::_('COM_USERS_MFA_EDIT_FIELD_TITLE_DESC')) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="com-users-is-default-method" <?php echo $this->record->default ? 'checked="checked"' : ''; ?> name="default">
|
||||
<label class="form-check-label" for="com-users-is-default-method">
|
||||
<?php echo Text::_('COM_USERS_MFA_EDIT_FIELD_DEFAULT'); ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($this->renderOptions['pre_message'])) : ?>
|
||||
<div class="com-users-method-edit-pre-message text-muted mt-4 mb-3">
|
||||
<?php echo $this->renderOptions['pre_message'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($this->renderOptions['tabular_data'])) : ?>
|
||||
<div class="com-users-method-edit-tabular-container">
|
||||
<?php if (!empty($this->renderOptions['table_heading'])) : ?>
|
||||
<h<?php echo $headingLevel ?> class="h3 border-bottom mb-3">
|
||||
<?php echo $this->renderOptions['table_heading'] ?>
|
||||
</h<?php echo $headingLevel ?>>
|
||||
<?php endif; ?>
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<?php foreach ($this->renderOptions['tabular_data'] as $cell1 => $cell2) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php echo $cell1 ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $cell2 ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->renderOptions['field_type'] == 'custom') : ?>
|
||||
<?php echo $this->renderOptions['html']; ?>
|
||||
<?php endif; ?>
|
||||
<div class="row mb-3 <?php echo $this->renderOptions['input_type'] === 'hidden' ? 'd-none' : '' ?>">
|
||||
<?php if ($this->renderOptions['label']) : ?>
|
||||
<label class="col-sm-3 col-form-label" for="com-users-method-code">
|
||||
<?php echo $this->renderOptions['label']; ?>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<div class="col-sm-9" <?php echo $this->renderOptions['label'] ? '' : 'offset-sm-3' ?>>
|
||||
<?php
|
||||
$attributes = array_merge(
|
||||
[
|
||||
'type' => $this->renderOptions['input_type'],
|
||||
'name' => 'code',
|
||||
'value' => $this->escape($this->renderOptions['input_value']),
|
||||
'id' => 'com-users-method-code',
|
||||
'class' => 'form-control',
|
||||
'aria-describedby' => 'com-users-method-code-help',
|
||||
],
|
||||
$this->renderOptions['input_attributes']
|
||||
);
|
||||
|
||||
if (strpos($attributes['class'], 'form-control') === false) {
|
||||
$attributes['class'] .= ' form-control';
|
||||
}
|
||||
?>
|
||||
<input <?php echo ArrayHelper::toString($attributes) ?>>
|
||||
<p class="form-text" id="com-users-method-code-help">
|
||||
<?php echo $this->escape($this->renderOptions['placeholder']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container d-sm-none">
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<button type="submit"
|
||||
id="user-mfa-edit-save"
|
||||
class="btn btn-primary me-3 <?php echo $hideSubmit ? 'd-none' : '' ?> <?php echo $this->renderOptions['submit_class'] ?>">
|
||||
<span class="<?php echo $this->renderOptions['submit_icon'] ?>" aria-hidden="true"></span>
|
||||
<?php echo Text::_($this->renderOptions['submit_text']); ?>
|
||||
</button>
|
||||
|
||||
<a href="<?php echo $cancelURL ?>"
|
||||
id="user-mfa-edit-cancel"
|
||||
class="btn btn-sm btn-danger">
|
||||
<span class="icon icon-cancel-2" aria-hidden="true"></span>
|
||||
<?php echo Text::_('JCANCEL'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($this->renderOptions['post_message'])) : ?>
|
||||
<div class="com-users-method-edit-post-message text-muted">
|
||||
<?php echo $this->renderOptions['post_message'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
54
administrator/components/com_users/tmpl/methods/default.php
Normal file
54
administrator/components/com_users/tmpl/methods/default.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Users\Administrator\View\Methods\HtmlView;
|
||||
|
||||
/** @var HtmlView $this */
|
||||
?>
|
||||
<div id="com-users-methods-list">
|
||||
<div id="com-users-methods-reset-container" class="d-flex align-items-center border border-1 rounded-3 p-2">
|
||||
<div id="com-users-methods-reset-message" class="flex-grow-1">
|
||||
<?php echo Text::_('COM_USERS_MFA_LIST_STATUS_' . ($this->mfaActive ? 'ON' : 'OFF')) ?>
|
||||
</div>
|
||||
<?php if ($this->mfaActive) : ?>
|
||||
<div>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&task=methods.disable&' . Factory::getApplication()->getFormToken() . '=1' . ($this->returnURL ? '&returnurl=' . $this->escape(urlencode($this->returnURL)) : '') . '&user_id=' . $this->user->id) ?>"
|
||||
class="btn btn-danger btn-sm">
|
||||
<?php echo Text::_('COM_USERS_MFA_LIST_REMOVEALL'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!count($this->methods)) : ?>
|
||||
<div id="com-users-methods-list-instructions" class="alert alert-info mt-2">
|
||||
<span class="icon icon-info-circle" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_MFA_LIST_INSTRUCTIONS'); ?>
|
||||
</div>
|
||||
<?php elseif ($this->isMandatoryMFASetup) : ?>
|
||||
<div class="alert alert-info my-3">
|
||||
<h3 class="alert-heading">
|
||||
<?php echo Text::_('COM_USERS_MFA_MANDATORY_NOTICE_HEAD') ?>
|
||||
</h3>
|
||||
<p>
|
||||
<?php echo Text::_('COM_USERS_MFA_MANDATORY_NOTICE_BODY') ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php $this->setLayout('list');
|
||||
echo $this->loadTemplate(); ?>
|
||||
</div>
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\Users\Administrator\View\Methods\HtmlView;
|
||||
|
||||
/** @var HtmlView $this */
|
||||
|
||||
$headingLevel = 2;
|
||||
?>
|
||||
<div id="com-users-methods-list">
|
||||
<?php if (!$this->isAdmin) : ?>
|
||||
<h<?php echo $headingLevel ?> id="com-users-methods-list-head">
|
||||
<?php echo Text::_('COM_USERS_MFA_FIRSTTIME_PAGE_HEAD'); ?>
|
||||
</h<?php echo $headingLevel++ ?>>
|
||||
<?php endif; ?>
|
||||
<div id="com-users-methods-list-instructions" class="alert alert-info">
|
||||
<h<?php echo $headingLevel ?> class="alert-heading">
|
||||
<span class="fa fa-shield-alt" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_USERS_MFA_FIRSTTIME_INSTRUCTIONS_HEAD'); ?>
|
||||
</h<?php echo $headingLevel ?>>
|
||||
<p>
|
||||
<?php echo Text::_('COM_USERS_MFA_FIRSTTIME_INSTRUCTIONS_WHATITDOES'); ?>
|
||||
</p>
|
||||
<a href="<?php echo Route::_(
|
||||
'index.php?option=com_users&task=methods.doNotShowThisAgain' .
|
||||
($this->returnURL ? '&returnurl=' . $this->escape(urlencode($this->returnURL)) : '') .
|
||||
'&user_id=' . $this->user->id .
|
||||
'&' . Factory::getApplication()->getFormToken() . '=1'
|
||||
)?>"
|
||||
class="btn btn-danger w-100">
|
||||
<?php echo Text::_('COM_USERS_MFA_FIRSTTIME_NOTINTERESTED'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php $this->setLayout('list');
|
||||
echo $this->loadTemplate(); ?>
|
||||
</div>
|
||||
144
administrator/components/com_users/tmpl/methods/list.php
Normal file
144
administrator/components/com_users/tmpl/methods/list.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Administrator
|
||||
* @subpackage com_users
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Component\Users\Administrator\Helper\Mfa as MfaHelper;
|
||||
use Joomla\Component\Users\Administrator\Model\MethodsModel;
|
||||
use Joomla\Component\Users\Administrator\View\Methods\HtmlView;
|
||||
|
||||
/** @var HtmlView $this */
|
||||
|
||||
HTMLHelper::_('bootstrap.tooltip', '.hasTooltip');
|
||||
|
||||
/** @var MethodsModel $model */
|
||||
$model = $this->getModel();
|
||||
|
||||
$this->document->getWebAssetManager()->useScript('com_users.two-factor-list');
|
||||
|
||||
$canAddEdit = MfaHelper::canAddEditMethod($this->user);
|
||||
$canDelete = MfaHelper::canDeleteMethod($this->user);
|
||||
?>
|
||||
<div id="com-users-methods-list-container">
|
||||
<?php foreach ($this->methods as $methodName => $method) :
|
||||
$methodClass = 'com-users-methods-list-method-name-' . htmlentities($method['name'])
|
||||
. ($this->defaultMethod == $methodName ? ' com-users-methods-list-method-default' : '');
|
||||
?>
|
||||
<div class="com-users-methods-list-method <?php echo $methodClass?> <?php echo count($method['active']) ? 'com-users-methods-list-method-active' : '' ?>">
|
||||
<div class="com-users-methods-list-method-header">
|
||||
<div class="com-users-methods-list-method-image">
|
||||
<img src="<?php echo Uri::root() . $method['image'] ?>"
|
||||
alt="<?php echo $this->escape($method['display']) ?>"
|
||||
class="img-fluid"
|
||||
>
|
||||
</div>
|
||||
<div class="com-users-methods-list-method-title">
|
||||
<h3>
|
||||
<span class="me-1 flex-grow-1">
|
||||
<?php echo $method['display'] ?>
|
||||
</span>
|
||||
<?php if ($this->defaultMethod == $methodName) : ?>
|
||||
<span id="com-users-methods-list-method-default-tag" class="badge bg-info p-2 fs-4 me-1">
|
||||
<?php echo Text::_('COM_USERS_MFA_LIST_DEFAULTTAG') ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="com-users-methods-list-method-records-container">
|
||||
<div class="com-users-methods-list-method-info">
|
||||
<?php echo $method['shortinfo'] ?>
|
||||
</div>
|
||||
|
||||
<?php if (count($method['active'])) : ?>
|
||||
<div class="com-users-methods-list-method-records pt-2 my-2">
|
||||
<?php foreach ($method['active'] as $record) : ?>
|
||||
<div class="com-users-methods-list-method-record d-flex flex-row flex-wrap justify-content-start border-top py-2">
|
||||
<div class="com-users-methods-list-method-record-info flex-grow-1 d-flex flex-column align-items-start gap-1">
|
||||
<?php if ($methodName === 'backupcodes') : ?>
|
||||
<?php if ($canAddEdit) : ?>
|
||||
<div class="alert alert-info mt-1 w-100">
|
||||
<?php echo Text::sprintf('COM_USERS_MFA_BACKUPCODES_PRINT_PROMPT_HEAD', Route::_('index.php?option=com_users&task=method.edit&id=' . (int) $record->id . ($this->returnURL ? '&returnurl=' . $this->escape(urlencode($this->returnURL)) : '') . '&user_id=' . $this->user->id)) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php else : ?>
|
||||
<h4 class="com-users-methods-list-method-record-title-container mb-1 fs-3">
|
||||
<?php if ($record->default) : ?>
|
||||
<span id="com-users-methods-list-method-default-badge-small"
|
||||
class="text-warning me-1 hasTooltip"
|
||||
title="<?php echo $this->escape(Text::_('COM_USERS_MFA_LIST_DEFAULTTAG')) ?>">
|
||||
<span class="icon icon-star" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo $this->escape(Text::_('COM_USERS_MFA_LIST_DEFAULTTAG')) ?></span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<span class="com-users-methods-list-method-record-title fw-bold">
|
||||
<?php echo $this->escape($record->title); ?>
|
||||
</span>
|
||||
</h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="com-users-methods-list-method-record-lastused my-1 d-flex flex-row flex-wrap justify-content-start gap-5 text-muted w-100">
|
||||
<span class="com-users-methods-list-method-record-createdon">
|
||||
<?php echo Text::sprintf('COM_USERS_MFA_LBL_CREATEDON', $model->formatRelative($record->created_on)) ?>
|
||||
</span>
|
||||
<span class="com-users-methods-list-method-record-lastused-date">
|
||||
<?php echo Text::sprintf('COM_USERS_MFA_LBL_LASTUSED', $model->formatRelative($record->last_used)) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php if ($methodName !== 'backupcodes' && ($canAddEdit || $canDelete)) : ?>
|
||||
<div class="com-users-methods-list-method-record-actions my-2 d-flex flex-row flex-wrap justify-content-center align-content-center align-items-start">
|
||||
<?php if ($canAddEdit) : ?>
|
||||
<a class="com-users-methods-list-method-record-edit btn btn-secondary btn-sm mx-1 hasTooltip"
|
||||
href="<?php echo Route::_('index.php?option=com_users&task=method.edit&id=' . (int) $record->id . ($this->returnURL ? '&returnurl=' . $this->escape(urlencode($this->returnURL)) : '') . '&user_id=' . $this->user->id)?>"
|
||||
title="<?php echo Text::_('JACTION_EDIT') ?> <?php echo $this->escape($record->title); ?>">
|
||||
<span class="icon icon-pencil" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('JACTION_EDIT') ?> <?php echo $this->escape($record->title); ?></span>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($method['canDisable'] && $canDelete) : ?>
|
||||
<a class="com-users-methods-list-method-record-delete btn btn-danger btn-sm mx-1 hasTooltip"
|
||||
href="<?php echo Route::_('index.php?option=com_users&task=method.delete&id=' . (int) $record->id . ($this->returnURL ? '&returnurl=' . $this->escape(urlencode($this->returnURL)) : '') . '&user_id=' . $this->user->id . '&' . Factory::getApplication()->getFormToken() . '=1')?>"
|
||||
title="<?php echo Text::_('JACTION_DELETE') ?> <?php echo $this->escape($record->title); ?>">
|
||||
<span class="icon icon-trash" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('JACTION_DELETE') ?> <?php echo $this->escape($record->title); ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canAddEdit && (empty($method['active']) || $method['allowMultiple'])) : ?>
|
||||
<div class="com-users-methods-list-method-addnew-container border-top pt-2">
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&task=method.add&method=' . $this->escape(urlencode($method['name'])) . ($this->returnURL ? '&returnurl=' . $this->escape(urlencode($this->returnURL)) : '') . '&user_id=' . $this->user->id)?>"
|
||||
class="com-users-methods-list-method-addnew btn btn-primary btn-sm"
|
||||
>
|
||||
<span class="icon-plus-2" aria-hidden="true"></span>
|
||||
<?php echo Text::sprintf('COM_USERS_MFA_ADD_AUTHENTICATOR_OF_TYPE', $method['display']) ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user