Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
43 changes: 0 additions & 43 deletions App/Controllers/ModuleUsersGroupsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -638,47 +638,4 @@ public function deleteAction(string $groupId): void
$this->deleteEntity($group, 'module-users-groups/module-users-groups/index');
}

/**
* Changes the default user group action.
*
* @return void
*/
public function changeDefaultAction(): void
{
if (!$this->request->isPost()) {
return;
}

// Get the POST data
$data = $this->request->getPost();

// Find all user groups
$groups = UsersGroups::find();
foreach ($groups as $group) {
// Check if the current group is the selected default group
if ($group->defaultGroup === '1' and $group->id !== $data['defaultGroup']) {
$group->defaultGroup = '0';
$this->saveEntity($group);
}
if ($group->defaultGroup !== '1' and $group->id === $data['defaultGroup']) {
$group->defaultGroup = '1';
$this->saveEntity($group);
}
}

// Get current user group memberships
$currentUsersGroups = GroupMembers::find()->toArray();
$users = Users::find();
foreach ($users as $user) {
// Check if the user is not already in a group
$key = array_search($user->id, array_column($currentUsersGroups, 'user_id'));
if (!$key) {
// Create a new group membership record
$record = new GroupMembers();
$record->group_id = $data['defaultGroup'];
$record->user_id = $user->id;
$this->saveEntity($record);
}
}
}
}
18 changes: 13 additions & 5 deletions App/Forms/DefaultGroupForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,34 @@ class DefaultGroupForm extends BaseForm

public function initialize($entity = null, $options = null): void
{
$variants = [];
$defaultGroupValue = null;
$usersGroups = UsersGroups::find();
$defaultGroupValue = '';

// Find default group value
foreach ($usersGroups as $usersGroup) {
$variants[$usersGroup->id] = $usersGroup->name;
if ($usersGroup->defaultGroup === '1') {
$defaultGroupValue = $usersGroup->id;
$defaultGroupValue = (string)$usersGroup->id;
break;
}
}

// Create select with groups as options
$defaultGroupSelect = new Select(
'defaultGroup', $variants, [
'defaultGroup',
$usersGroups,
[
'using' => [
'id',
'name',
],
'useEmpty' => true,
'emptyText' => 'Choose...',
'emptyValue' => '',
'value' => $defaultGroupValue,
'class' => 'ui selection dropdown search select-default-group',
]
);

$this->add($defaultGroupSelect);
}
}
72 changes: 18 additions & 54 deletions App/Forms/ExtensionEditAdditionalForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,63 +21,27 @@

use MikoPBX\AdminCabinet\Forms\BaseForm;
use MikoPBX\AdminCabinet\Forms\ExtensionEditForm;
use Modules\ModuleUsersGroups\Models\GroupMembers;
use Modules\ModuleUsersGroups\Models\UsersGroups as ModelUsersGroups;
use Phalcon\Forms\Element\Select;
use Phalcon\Forms\Element\Hidden;


class ExtensionEditAdditionalForm extends BaseForm
{
public static function prepareAdditionalFields(ExtensionEditForm $form, \stdClass $entity, array $options = []){

// Prepare groups for select
$parameters = [
'columns' => [
'id',
'name'
]
];
$arrGroups = ModelUsersGroups::find($parameters);
$arrGroupsForSelect = [];
foreach ($arrGroups as $group) {
$arrGroupsForSelect[$group->id] = $group->name;
}

// Find current value
$userGroupId = null;
if (isset($entity->user_id)) {
$parameters = [
'conditions' => 'user_id = :user_id:',
'bind' => ['user_id' => $entity->user_id]
];

$curUserGroup = GroupMembers::findFirst($parameters);
if ($curUserGroup !== null) {
// Get the group ID from the existing group membership
$userGroupId = $curUserGroup->group_id;
} else {
// Get the group ID from the default group
$defaultGroup = ModelUsersGroups::findFirst('defaultGroup=1');
if ($defaultGroup){
$userGroupId = $defaultGroup->id;
}
}
}

$groupForSelect = new Select(
'mod_usrgr_select_group', $arrGroupsForSelect, [
'using' => [
'id',
'name',
],
'value' => $userGroupId,
'useEmpty' => false,
'class' => 'ui selection dropdown search select-group-field',
]
);

// Add the group select field to the form
$form->add($groupForSelect);
}

public static function prepareAdditionalFields(ExtensionEditForm $form, \stdClass $entity, array $options = []): void
{
// Add hidden field for the group ID (value will be set by JavaScript)
$form->add(new Hidden('mod_usrgr_select_group', [
'value' => '',
'id' => 'mod_usrgr_select_group'
]));

// Get all groups for dropdown (will be used in Volt template)
$groups = ModelUsersGroups::find([
'columns' => ['id', 'name'],
'order' => 'name ASC'
]);

// Store groups data in form for access in Volt template
$form->setUserOption('mod_usrgr_groups', $groups);
}
}
15 changes: 14 additions & 1 deletion App/Views/Extensions/modify.volt
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
<div class="two fields">
<div class="field">
<label for="mod_usrgr_select_group">{{ t._('mod_usrgr_SelectUserGroup') }}</label>
<label for="mod_usrgr_select_group_dropdown">{{ t._('mod_usrgr_SelectUserGroup') }}</label>
{{ form.render('mod_usrgr_select_group') }}
<div class="ui selection dropdown search select-group-field" id="mod_usrgr_select_group_dropdown">
<i class="dropdown icon"></i>
<div class="default text">{{ t._('mod_usrgr_SelectUserGroup') }}</div>
<div class="menu">
{% set groups = form.getUserOption('mod_usrgr_groups') %}
{% set selectedGroupId = form.getUserOption('mod_usrgr_selected_group_id') %}
{% if groups is not empty %}
{% for group in groups %}
<div class="item" data-value="{{ group.id }}">{{ group.name }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
89 changes: 89 additions & 0 deletions Lib/RestAPI/UsersGroups/CleanupOrphanedMembersAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
/*
* MikoPBX - free phone system for small business
* Copyright © 2017-2025 Alexey Portnov and Nikolay Beketov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/

namespace Modules\ModuleUsersGroups\Lib\RestAPI\UsersGroups;

use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
use MikoPBX\Common\Models\Users;
use Modules\ModuleUsersGroups\Models\GroupMembers;

/**
* Cleanup orphaned group member records
*
* @package Modules\ModuleUsersGroups\Lib\RestAPI\UsersGroups
*/
class CleanupOrphanedMembersAction
{
/**
* Remove group member records for deleted users
*
* @param array $data Request data (empty)
* @return PBXApiResult
*/
public static function main(array $data): PBXApiResult
{
$result = new PBXApiResult();
$result->processor = __METHOD__;

try {
// Get valid user IDs using simple find
$validUsers = Users::find(['columns' => 'id']);

if (count($validUsers) === 0) {
$result->success = true;
$result->data = [
'deleted' => 0,
'message' => 'No users in system'
];
$result->httpCode = 200;
return $result;
}

// Build list of valid user IDs
$validIds = [];
foreach ($validUsers as $user) {
$validIds[] = (int)$user->id;
}

// Get module database connection through model instance
$groupMember = new GroupMembers();
$connection = $groupMember->getReadConnection();
$validIdsList = implode(',', $validIds);

// Use direct SQL DELETE for performance
$sql = "DELETE FROM m_ModuleUsersGroups_GroupMembers WHERE user_id NOT IN ({$validIdsList})";
$success = $connection->execute($sql);
$deletedCount = $success ? $connection->affectedRows() : 0;

$result->success = true;
$result->data = [
'deleted' => $deletedCount,
'valid_users' => count($validIds)
];
$result->httpCode = 200;

} catch (\Throwable $e) {
$result->success = false;
$result->messages[] = 'Failed to cleanup orphaned members: ' . $e->getMessage();
$result->httpCode = 500;
}

return $result;
}
}
92 changes: 92 additions & 0 deletions Lib/RestAPI/UsersGroups/DataStructure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
/*
* MikoPBX - free phone system for small business
* Copyright © 2017-2025 Alexey Portnov and Nikolay Beketov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/

namespace Modules\ModuleUsersGroups\Lib\RestAPI\UsersGroups;

/**
* Data structure for Users Groups API
*
* @package Modules\ModuleUsersGroups\Lib\RestAPI\UsersGroups
*/
class DataStructure
{
/**
* Get parameter definitions for Users Groups API
*
* @return array<string, array<string, array<string, mixed>>>
*/
public static function getParameterDefinitions(): array
{
return [
'request' => [
'user_id' => [
'type' => 'integer',
'description' => 'User ID from Users table',
'sanitize' => 'int',
'minimum' => 1,
'example' => 1
],
'group_id' => [
'type' => 'integer',
'description' => 'Group ID from UsersGroups table',
'sanitize' => 'int',
'minimum' => 1,
'required' => true,
'example' => 1
]
],
'response' => [
'group_id' => [
'type' => 'integer',
'description' => 'Group ID',
'example' => 1
]
]
];
}

/**
* Get sanitization rules auto-generated from definitions
*
* @return array<string, string>
*/
public static function getSanitizationRules(): array
{
$definitions = static::getParameterDefinitions();
$rules = [];

foreach ($definitions['request'] as $field => $def) {
$rule = [$def['type'] ?? 'string'];

if (isset($def['sanitize'])) {
$rule[] = 'sanitize:' . $def['sanitize'];
}
if (isset($def['maxLength'])) {
$rule[] = 'max:' . $def['maxLength'];
}
if (!isset($def['required'])) {
$rule[] = 'empty_to_null';
}

$rules[$field] = implode('|', $rule);
}

return $rules;
}
}
Loading