aboutsummaryrefslogtreecommitdiffhomepage
path: root/main/app/sprinkles/admin
diff options
context:
space:
mode:
authormarvin-borner@live.com2018-04-16 21:09:05 +0200
committermarvin-borner@live.com2018-04-16 21:09:05 +0200
commitcf14306c2b3f82a81f8d56669a71633b4d4b5fce (patch)
tree86700651aa180026e89a66064b0364b1e4346f3f /main/app/sprinkles/admin
parent619b01b3615458c4ed78bfaeabb6b1a47cc8ad8b (diff)
Main merge to user management system - files are now at /main/public/
Diffstat (limited to 'main/app/sprinkles/admin')
-rwxr-xr-xmain/app/sprinkles/admin/asset-bundles.json170
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/css/tablesorter-custom.css30
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/activities.js16
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js49
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/group.js24
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/groups.js24
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/permission.js20
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/permissions.js16
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/role.js23
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/roles.js24
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/user.js25
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/pages/users.js24
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js111
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js148
-rwxr-xr-xmain/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js277
-rwxr-xr-xmain/app/sprinkles/admin/composer.json22
-rwxr-xr-xmain/app/sprinkles/admin/locale/ar/messages.php135
-rwxr-xr-xmain/app/sprinkles/admin/locale/de_DE/messages.php161
-rwxr-xr-xmain/app/sprinkles/admin/locale/en_US/messages.php160
-rwxr-xr-xmain/app/sprinkles/admin/locale/es_ES/messages.php164
-rwxr-xr-xmain/app/sprinkles/admin/locale/fa/messages.php158
-rwxr-xr-xmain/app/sprinkles/admin/locale/fr_FR/messages.php147
-rwxr-xr-xmain/app/sprinkles/admin/locale/it_IT/messages.php160
-rwxr-xr-xmain/app/sprinkles/admin/locale/pt_PT/messages.php139
-rwxr-xr-xmain/app/sprinkles/admin/locale/ru_RU/messages.php160
-rwxr-xr-xmain/app/sprinkles/admin/locale/th_TH/messages.php134
-rwxr-xr-xmain/app/sprinkles/admin/locale/tr/messages.php160
-rwxr-xr-xmain/app/sprinkles/admin/locale/zh_CN/messages.php161
-rwxr-xr-xmain/app/sprinkles/admin/routes/activities.php19
-rwxr-xr-xmain/app/sprinkles/admin/routes/admin.php23
-rwxr-xr-xmain/app/sprinkles/admin/routes/groups.php39
-rwxr-xr-xmain/app/sprinkles/admin/routes/permissions.php25
-rwxr-xr-xmain/app/sprinkles/admin/routes/roles.php45
-rwxr-xr-xmain/app/sprinkles/admin/routes/users.php51
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/group/create.yaml35
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/group/edit-info.yaml27
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/group/get-by-slug.yaml6
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/role/create.yaml26
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/role/edit-field.yaml24
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/role/edit-info.yaml20
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/role/get-by-slug.yaml6
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/user/create.yaml72
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/user/edit-field.yaml60
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/user/edit-info.yaml36
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/user/edit-password.yaml30
-rwxr-xr-xmain/app/sprinkles/admin/schema/requests/user/get-by-username.yaml6
-rwxr-xr-xmain/app/sprinkles/admin/src/Admin.php20
-rwxr-xr-xmain/app/sprinkles/admin/src/Controller/ActivityController.php85
-rwxr-xr-xmain/app/sprinkles/admin/src/Controller/AdminController.php150
-rwxr-xr-xmain/app/sprinkles/admin/src/Controller/GroupController.php725
-rwxr-xr-xmain/app/sprinkles/admin/src/Controller/PermissionController.php206
-rwxr-xr-xmain/app/sprinkles/admin/src/Controller/RoleController.php930
-rwxr-xr-xmain/app/sprinkles/admin/src/Controller/UserController.php1261
-rwxr-xr-xmain/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php84
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php80
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/GroupSprunje.php42
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php93
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php48
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/RoleSprunje.php67
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php48
-rwxr-xr-xmain/app/sprinkles/admin/src/Sprunje/UserSprunje.php185
-rwxr-xr-xmain/app/sprinkles/admin/templates/forms/group.html.twig69
-rwxr-xr-xmain/app/sprinkles/admin/templates/forms/role.html.twig56
-rwxr-xr-xmain/app/sprinkles/admin/templates/forms/user.html.twig125
-rwxr-xr-xmain/app/sprinkles/admin/templates/mail/password-create.html.twig19
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/confirm-clear-cache.html.twig17
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/confirm-delete-group.html.twig17
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/confirm-delete-role.html.twig17
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/confirm-delete-user.html.twig17
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/group.html.twig7
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/role-manage-permissions.html.twig94
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/role.html.twig7
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/user-manage-roles.html.twig77
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/user-set-password.html.twig62
-rwxr-xr-xmain/app/sprinkles/admin/templates/modals/user.html.twig7
-rwxr-xr-xmain/app/sprinkles/admin/templates/navigation/navbar.html.twig15
-rwxr-xr-xmain/app/sprinkles/admin/templates/navigation/sidebar-menu.html.twig43
-rwxr-xr-xmain/app/sprinkles/admin/templates/navigation/sidebar-user.html.twig10
-rwxr-xr-xmain/app/sprinkles/admin/templates/navigation/sidebar.html.twig10
-rwxr-xr-xmain/app/sprinkles/admin/templates/navigation/user-card.html.twig8
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/abstract/dashboard.html.twig87
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/activities.html.twig46
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/dashboard.html.twig282
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/group.html.twig106
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/groups.html.twig52
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/permission.html.twig91
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/permissions.html.twig45
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/role.html.twig129
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/roles.html.twig50
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/user.html.twig195
-rwxr-xr-xmain/app/sprinkles/admin/templates/pages/users.html.twig53
-rwxr-xr-xmain/app/sprinkles/admin/templates/tables/activities.html.twig73
-rwxr-xr-xmain/app/sprinkles/admin/templates/tables/groups.html.twig69
-rwxr-xr-xmain/app/sprinkles/admin/templates/tables/permissions.html.twig66
-rwxr-xr-xmain/app/sprinkles/admin/templates/tables/roles.html.twig74
-rwxr-xr-xmain/app/sprinkles/admin/templates/tables/users.html.twig149
-rwxr-xr-xmain/app/sprinkles/admin/tests/Integration/SprunjeTests.php111
97 files changed, 9771 insertions, 0 deletions
diff --git a/main/app/sprinkles/admin/asset-bundles.json b/main/app/sprinkles/admin/asset-bundles.json
new file mode 100755
index 0000000..1bc1706
--- /dev/null
+++ b/main/app/sprinkles/admin/asset-bundles.json
@@ -0,0 +1,170 @@
+{
+ "bundle": {
+ "js/admin": {
+ "scripts": [
+ "vendor/moment/moment.js",
+ "userfrosting/js/handlebars-helpers.js",
+ "vendor/tablesorter/dist/js/jquery.tablesorter.js",
+ "vendor/tablesorter/dist/js/jquery.tablesorter.widgets.js",
+ "userfrosting/js/tablesorter/widget-sort2Hash.js",
+ "vendor/tablesorter/dist/js/widgets/widget-columnSelector.min.js",
+ "vendor/tablesorter/dist/js/widgets/widget-reflow.min.js",
+ "vendor/tablesorter/dist/js/widgets/widget-pager.min.js",
+ "userfrosting/js/query-string.js",
+ "userfrosting/js/uf-table.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/activities": {
+ "scripts": [
+ "userfrosting/js/pages/activities.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/dashboard": {
+ "scripts": [
+ "userfrosting/js/widgets/users.js",
+ "userfrosting/js/pages/dashboard.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/group": {
+ "scripts": [
+ "userfrosting/js/widgets/users.js",
+ "userfrosting/js/widgets/groups.js",
+ "userfrosting/js/pages/group.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/permission": {
+ "scripts": [
+ "userfrosting/js/widgets/users.js",
+ "userfrosting/js/pages/permission.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/role": {
+ "scripts": [
+ "userfrosting/js/widgets/roles.js",
+ "userfrosting/js/pages/role.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/user": {
+ "scripts": [
+ "userfrosting/js/widgets/users.js",
+ "userfrosting/js/pages/user.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/users": {
+ "scripts": [
+ "userfrosting/js/widgets/users.js",
+ "userfrosting/js/pages/users.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/groups": {
+ "scripts": [
+ "userfrosting/js/widgets/groups.js",
+ "userfrosting/js/pages/groups.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/permissions": {
+ "scripts": [
+ "userfrosting/js/pages/permissions.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "js/pages/roles": {
+ "scripts": [
+ "userfrosting/js/widgets/roles.js",
+ "userfrosting/js/pages/roles.js"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "scripts": "plain"
+ }
+ }
+ }
+ },
+ "css/admin": {
+ "styles": [
+ "font-starcraft/css/font-starcraft.css",
+ "vendor/tablesorter/dist/css/theme.bootstrap.min.css",
+ "vendor/tablesorter/dist/css/jquery.tablesorter.pager.min.css",
+ "userfrosting/css/tablesorter-reflow.css",
+ "userfrosting/css/tablesorter-custom.css"
+ ],
+ "options": {
+ "result": {
+ "type": {
+ "styles": "plain"
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/assets/userfrosting/css/tablesorter-custom.css b/main/app/sprinkles/admin/assets/userfrosting/css/tablesorter-custom.css
new file mode 100755
index 0000000..405c1ca
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/css/tablesorter-custom.css
@@ -0,0 +1,30 @@
+/* ==========================================================================
+ Custom styling for tablesorter tables
+ ========================================================================== */
+
+/* Custom styling for pager controls
+ ========================================================================== */
+
+.pager-lg {
+ font-size: large;
+}
+
+.pager-control {
+ font-size: x-large;
+ padding: 4px 8px;
+ cursor: pointer;
+}
+
+/* Custom styling for panels that contain buttons in the header (eg, CSV download buttons)
+ ========================================================================== */
+
+.panel-heading-buttons h3 {
+ padding-top: 7.5px;
+}
+
+/* Don't display tablesorter filter field when disabled
+ ========================================================================== */
+
+.tablesorter thead .disabled {
+ display: none
+} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/activities.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/activities.js
new file mode 100755
index 0000000..89ac5c1
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/activities.js
@@ -0,0 +1,16 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /activities
+ */
+
+$(document).ready(function() {
+ // Set up table of activities
+ $("#widget-activities").ufTable({
+ dataUrl: site.uri.public + "/api/activities",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js
new file mode 100755
index 0000000..f2b8a4f
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/dashboard.js
@@ -0,0 +1,49 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * Target page: /dashboard
+ */
+
+$(document).ready(function() {
+ $('.js-clear-cache').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/dashboard/clear-cache",
+ ajaxParams: {
+ slug: $(this).data('slug')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ form.ufForm()
+ .on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+ });
+
+ // Table of site activities
+ $("#widget-activities").ufTable({
+ dataUrl: site.uri.public + "/api/activities",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Table of users in current user's group
+ $("#widget-group-users").ufTable({
+ dataUrl: site.uri.public + "/api/groups/g/" + page.group_slug + "/users",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Bind user creation button
+ bindUserCreationButton($("#widget-group-users"));
+
+ // Bind user table buttons
+ $("#widget-group-users").on("pagerComplete.ufTable", function () {
+ bindUserButtons($(this));
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/group.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/group.js
new file mode 100755
index 0000000..a1ca959
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/group.js
@@ -0,0 +1,24 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /groups/g/{slug}
+ */
+
+$(document).ready(function() {
+ // Control buttons
+ bindGroupButtons($("#view-group"));
+
+ // Table of users in this group
+ $("#widget-group-users").ufTable({
+ dataUrl: site.uri.public + '/api/groups/g/' + page.group_slug + '/users',
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Bind user table buttons
+ $("#widget-group-users").on("pagerComplete.ufTable", function () {
+ bindUserButtons($(this));
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/groups.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/groups.js
new file mode 100755
index 0000000..0bfc65a
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/groups.js
@@ -0,0 +1,24 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on widgets/groups.js, uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /groups
+ */
+
+$(document).ready(function() {
+
+ $("#widget-groups").ufTable({
+ dataUrl: site.uri.public + "/api/groups",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Bind creation button
+ bindGroupCreationButton($("#widget-groups"));
+
+ // Bind table buttons
+ $("#widget-groups").on("pagerComplete.ufTable", function () {
+ bindGroupButtons($(this));
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/permission.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/permission.js
new file mode 100755
index 0000000..87e851f
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/permission.js
@@ -0,0 +1,20 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /permissions/p/{id}
+ */
+
+$(document).ready(function() {
+ $("#widget-permission-users").ufTable({
+ dataUrl: site.uri.public + '/api/permissions/p/' + page.permission_id + '/users',
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Bind table buttons
+ $("#widget-permission-users").on("pagerComplete.ufTable", function () {
+ bindUserButtons($(this));
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/permissions.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/permissions.js
new file mode 100755
index 0000000..6266ff4
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/permissions.js
@@ -0,0 +1,16 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on widgets/permissions.js, uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /permissions
+ */
+
+$(document).ready(function() {
+ // Set up table of permissions
+ $("#widget-permissions").ufTable({
+ dataUrl: site.uri.public + "/api/permissions",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/role.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/role.js
new file mode 100755
index 0000000..8dae7f5
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/role.js
@@ -0,0 +1,23 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /roles/r/{slug}
+ */
+
+$(document).ready(function() {
+ // Control buttons
+ bindRoleButtons($("#view-role"));
+
+ $("#widget-role-permissions").ufTable({
+ dataUrl: site.uri.public + '/api/roles/r/' + page.role_slug + '/permissions',
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ $("#widget-role-users").ufTable({
+ dataUrl: site.uri.public + '/api/roles/r/' + page.role_slug + '/users',
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/roles.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/roles.js
new file mode 100755
index 0000000..b1febb2
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/roles.js
@@ -0,0 +1,24 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on widgets/roles.js, uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /roles
+ */
+
+$(document).ready(function() {
+ // Set up table of roles
+ $("#widget-roles").ufTable({
+ dataUrl: site.uri.public + "/api/roles",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Bind creation button
+ bindRoleCreationButton($("#widget-roles"));
+
+ // Bind table buttons
+ $("#widget-roles").on("pagerComplete.ufTable", function () {
+ bindRoleButtons($(this));
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/user.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/user.js
new file mode 100755
index 0000000..70acf7c
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/user.js
@@ -0,0 +1,25 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /users/u/{user_name}
+ */
+
+$(document).ready(function() {
+ // Control buttons
+ bindUserButtons($("#view-user"));
+
+ // Table of activities
+ $("#widget-user-activities").ufTable({
+ dataUrl: site.uri.public + '/api/users/u/' + page.user_name + '/activities',
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Table of permissions
+ $("#widget-permissions").ufTable({
+ dataUrl: site.uri.public + '/api/users/u/' + page.user_name + '/permissions',
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/pages/users.js b/main/app/sprinkles/admin/assets/userfrosting/js/pages/users.js
new file mode 100755
index 0000000..d9e4bb7
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/pages/users.js
@@ -0,0 +1,24 @@
+/**
+ * Page-specific Javascript file. Should generally be included as a separate asset bundle in your page template.
+ * example: {{ assets.js('js/pages/sign-in-or-register') | raw }}
+ *
+ * This script depends on widgets/users.js, uf-table.js, moment.js, handlebars-helpers.js
+ *
+ * Target page: /users
+ */
+
+$(document).ready(function() {
+ // Set up table of users
+ $("#widget-users").ufTable({
+ dataUrl: site.uri.public + "/api/users",
+ useLoadingTransition: site.uf_table.use_loading_transition
+ });
+
+ // Bind creation button
+ bindUserCreationButton($("#widget-users"));
+
+ // Bind table buttons
+ $("#widget-users").on("pagerComplete.ufTable", function () {
+ bindUserButtons($(this));
+ });
+});
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js b/main/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js
new file mode 100755
index 0000000..d701d81
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/widgets/groups.js
@@ -0,0 +1,111 @@
+/**
+ * Groups widget. Sets up dropdowns, modals, etc for a table of groups.
+ */
+
+/**
+ * Set up the form in a modal after being successfully attached to the body.
+ */
+function attachGroupForm() {
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ /**
+ * Set up modal widgets
+ */
+ // Set up any widgets inside the modal
+ form.find(".js-select2").select2({
+ width: '100%'
+ });
+
+ // Auto-generate slug
+ form.find('input[name=name]').on('input change', function() {
+ var manualSlug = form.find('#form-group-slug-override').prop('checked');
+ if (!manualSlug) {
+ var slug = getSlug($(this).val());
+ form.find('input[name=slug]').val(slug);
+ }
+ });
+
+ form.find('#form-group-slug-override').on('change', function() {
+ if ($(this).prop('checked')) {
+ form.find('input[name=slug]').prop('readonly', false);
+ } else {
+ form.find('input[name=slug]').prop('readonly', true);
+ form.find('input[name=name]').trigger('change');
+ }
+ });
+
+ // Set icon when changed
+ form.find('input[name=icon]').on('input change', function() {
+ $(this).prev(".icon-preview").find("i").removeClass().addClass($(this).val());
+ });
+
+ // Set up the form for submission
+ form.ufForm({
+ validators: page.validators
+ }).on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+}
+
+/**
+ * Link group action buttons, for example in a table or on a specific group's page.
+ */
+function bindGroupButtons(el) {
+ /**
+ * Link row buttons after table is loaded.
+ */
+
+ /**
+ * Buttons that launch a modal dialog
+ */
+ // Edit group details button
+ el.find('.js-group-edit').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/groups/edit",
+ ajaxParams: {
+ slug: $(this).data('slug')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ attachGroupForm();
+ });
+
+ // Delete group button
+ el.find('.js-group-delete').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/groups/confirm-delete",
+ ajaxParams: {
+ slug: $(this).data('slug')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ form.ufForm()
+ .on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+ });
+}
+
+function bindGroupCreationButton(el) {
+ // Link create button
+ el.find('.js-group-create').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/groups/create",
+ msgTarget: $("#alerts-page")
+ });
+
+ attachGroupForm();
+ });
+};
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js b/main/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js
new file mode 100755
index 0000000..0e32651
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/widgets/roles.js
@@ -0,0 +1,148 @@
+/**
+ * Roles widget. Sets up dropdowns, modals, etc for a table of roles.
+ */
+
+/**
+ * Set up the form in a modal after being successfully attached to the body.
+ */
+function attachRoleForm() {
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ /**
+ * Set up modal widgets
+ */
+
+ // Auto-generate slug
+ form.find('input[name=name]').on('input change', function() {
+ var manualSlug = form.find('#form-role-slug-override').prop('checked');
+ if (!manualSlug) {
+ var slug = getSlug($(this).val());
+ form.find('input[name=slug]').val(slug);
+ }
+ });
+
+ form.find('#form-role-slug-override').on('change', function() {
+ if ($(this).prop('checked')) {
+ form.find('input[name=slug]').prop('readonly', false);
+ } else {
+ form.find('input[name=slug]').prop('readonly', true);
+ form.find('input[name=name]').trigger('change');
+ }
+ });
+
+ // Set up the form for submission
+ form.ufForm({
+ validators: page.validators
+ }).on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+}
+
+/**
+ * Link role action buttons, for example in a table or on a specific role's page.
+ */
+function bindRoleButtons(el) {
+ /**
+ * Link row buttons after table is loaded.
+ */
+
+ // Manage permissions button
+ el.find('.js-role-permissions').click(function() {
+ var slug = $(this).data('slug');
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/roles/permissions",
+ ajaxParams: {
+ slug: slug
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ // Set up collection widget
+ var permissionWidget = modal.find('.js-form-permissions');
+ permissionWidget.ufCollection({
+ dropdown: {
+ ajax: {
+ url : site.uri.public + '/api/permissions'
+ },
+ placeholder : "Select a permission"
+ },
+ dropdownTemplate: modal.find('#role-permissions-select-option').html(),
+ rowTemplate : modal.find('#role-permissions-row').html()
+ });
+
+ // Get current roles and add to widget
+ $.getJSON(site.uri.public + '/api/roles/r/' + slug + '/permissions')
+ .done(function (data) {
+ $.each(data.rows, function (idx, permission) {
+ permission.text = permission.name;
+ permissionWidget.ufCollection('addRow', permission);
+ });
+ });
+
+ // Set up form for submission
+ form.ufForm({
+ }).on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+ });
+
+ /**
+ * Buttons that launch a modal dialog
+ */
+ // Edit role details button
+ el.find('.js-role-edit').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/roles/edit",
+ ajaxParams: {
+ slug: $(this).data('slug')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ attachRoleForm();
+ });
+
+ // Delete role button
+ el.find('.js-role-delete').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/roles/confirm-delete",
+ ajaxParams: {
+ slug: $(this).data('slug')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ form.ufForm()
+ .on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+ });
+}
+
+function bindRoleCreationButton(el) {
+ // Link create button
+ el.find('.js-role-create').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/roles/create",
+ msgTarget: $("#alerts-page")
+ });
+
+ attachRoleForm();
+ });
+};
diff --git a/main/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js b/main/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js
new file mode 100755
index 0000000..2e153e5
--- /dev/null
+++ b/main/app/sprinkles/admin/assets/userfrosting/js/widgets/users.js
@@ -0,0 +1,277 @@
+/**
+ * Users widget. Sets up dropdowns, modals, etc for a table of users.
+ */
+
+/**
+ * Set up the form in a modal after being successfully attached to the body.
+ */
+function attachUserForm() {
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ // Set up any widgets inside the modal
+ form.find(".js-select2").select2({
+ width: '100%'
+ });
+
+ // Set up the form for submission
+ form.ufForm({
+ validators: page.validators
+ }).on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+}
+
+/**
+ * Enable/disable password fields when switch is toggled
+ */
+function toggleChangePasswordMode(el, userName, changePasswordMode) {
+ var form = el.find("form");
+ if (changePasswordMode == 'link') {
+ $(".controls-password").find("input[type='password']").prop('disabled', true);
+ // Form submits password reset request
+ form.attr({
+ method: 'POST',
+ action: site.uri.public + '/api/users/u/' + userName + '/password-reset'
+ });
+
+ var validator = form.validate();
+ if (validator) {
+ //Iterate through named elements inside of the form, and mark them as error free
+ el.find("input[type='password']").each(function() {
+ validator.successList.push(this); //mark as error free
+ });
+ validator.resetForm();//remove error class on name elements and clear history
+ validator.reset();//remove all error and success data
+ }
+ el.find("input[type='password']").closest('.form-group')
+ .removeClass('has-error has-success');
+ el.find('.form-control-feedback').each(function () {
+ $(this).remove();
+ });
+ } else {
+ $(".controls-password").find("input[type='password']").prop('disabled', false);
+ // Form submits direct password update
+ form.attr({
+ method: 'PUT',
+ action: site.uri.public + '/api/users/u/' + userName + '/password'
+ });
+ }
+}
+
+/**
+ * Update user field(s)
+ */
+function updateUser(userName, fieldName, fieldValue) {
+ var data = {
+ 'value': fieldValue
+ };
+
+ data[site.csrf.keys.name] = site.csrf.name;
+ data[site.csrf.keys.value] = site.csrf.value;
+
+ var url = site.uri.public + '/api/users/u/' + userName + '/' + fieldName;
+ var debugAjax = (typeof site !== "undefined") && site.debug.ajax;
+
+ return $.ajax({
+ type: "PUT",
+ url: url,
+ data: data,
+ dataType: debugAjax ? 'html' : 'json',
+ converters: {
+ // Override jQuery's strict JSON parsing
+ 'text json': function(result) {
+ try {
+ // First try to use native browser parsing
+ if (typeof JSON === 'object' && typeof JSON.parse === 'function') {
+ return JSON.parse(result);
+ } else {
+ return $.parseJSON(result);
+ }
+ } catch (e) {
+ // statements to handle any exceptions
+ console.log("Warning: Could not parse expected JSON response.");
+ return {};
+ }
+ }
+ }
+ }).fail(function (jqXHR) {
+ // Error messages
+ if (debugAjax && jqXHR.responseText) {
+ document.write(jqXHR.responseText);
+ document.close();
+ } else {
+ console.log("Error (" + jqXHR.status + "): " + jqXHR.responseText );
+
+ // Display errors on failure
+ // TODO: ufAlerts widget should have a 'destroy' method
+ if (!$("#alerts-page").data('ufAlerts')) {
+ $("#alerts-page").ufAlerts();
+ } else {
+ $("#alerts-page").ufAlerts('clear');
+ }
+
+ $("#alerts-page").ufAlerts('fetch').ufAlerts('render');
+ }
+
+ return jqXHR;
+ }).done(function (response) {
+ window.location.reload();
+ });
+}
+
+/**
+ * Link user action buttons, for example in a table or on a specific user's page.
+ */
+ function bindUserButtons(el) {
+
+ /**
+ * Buttons that launch a modal dialog
+ */
+ // Edit general user details button
+ el.find('.js-user-edit').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/users/edit",
+ ajaxParams: {
+ user_name: $(this).data('user_name')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ attachUserForm();
+ });
+
+ // Manage user roles button
+ el.find('.js-user-roles').click(function() {
+ var userName = $(this).data('user_name');
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/users/roles",
+ ajaxParams: {
+ user_name: userName
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ // Set up collection widget
+ var roleWidget = modal.find('.js-form-roles');
+ roleWidget.ufCollection({
+ dropdown : {
+ ajax: {
+ url : site.uri.public + '/api/roles'
+ },
+ placeholder : "Select a role"
+ },
+ dropdownTemplate: modal.find('#user-roles-select-option').html(),
+ rowTemplate : modal.find('#user-roles-row').html()
+ });
+
+ // Get current roles and add to widget
+ $.getJSON(site.uri.public + '/api/users/u/' + userName + '/roles')
+ .done(function (data) {
+ $.each(data.rows, function (idx, role) {
+ role.text = role.name;
+ roleWidget.ufCollection('addRow', role);
+ });
+ });
+
+ // Set up form for submission
+ form.ufForm({
+ }).on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+ });
+
+ // Change user password button
+ el.find('.js-user-password').click(function() {
+ var userName = $(this).data('user_name');
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/users/password",
+ ajaxParams: {
+ user_name: userName
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ // Set up form for submission
+ form.ufForm({
+ validators: page.validators
+ }).on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+
+ toggleChangePasswordMode(modal, userName, 'link');
+
+ // On submission, submit either the PUT request, or POST for a password reset, depending on the toggle state
+ modal.find("input[name='change_password_mode']").click(function() {
+ var changePasswordMode = $(this).val();
+ toggleChangePasswordMode(modal, userName, changePasswordMode);
+ });
+ });
+ });
+
+ // Delete user button
+ el.find('.js-user-delete').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/users/confirm-delete",
+ ajaxParams: {
+ user_name: $(this).data('user_name')
+ },
+ msgTarget: $("#alerts-page")
+ });
+
+ $("body").on('renderSuccess.ufModal', function (data) {
+ var modal = $(this).ufModal('getModal');
+ var form = modal.find('.js-form');
+
+ form.ufForm()
+ .on("submitSuccess.ufForm", function() {
+ // Reload page on success
+ window.location.reload();
+ });
+ });
+ });
+
+ /**
+ * Direct action buttons
+ */
+ el.find('.js-user-activate').click(function() {
+ var btn = $(this);
+ updateUser(btn.data('user_name'), 'flag_verified', '1');
+ });
+
+ el.find('.js-user-enable').click(function () {
+ var btn = $(this);
+ updateUser(btn.data('user_name'), 'flag_enabled', '1');
+ });
+
+ el.find('.js-user-disable').click(function () {
+ var btn = $(this);
+ updateUser(btn.data('user_name'), 'flag_enabled', '0');
+ });
+}
+
+function bindUserCreationButton(el) {
+ // Link create button
+ el.find('.js-user-create').click(function() {
+ $("body").ufModal({
+ sourceUrl: site.uri.public + "/modals/users/create",
+ msgTarget: $("#alerts-page")
+ });
+
+ attachUserForm();
+ });
+};
diff --git a/main/app/sprinkles/admin/composer.json b/main/app/sprinkles/admin/composer.json
new file mode 100755
index 0000000..8ccd5c0
--- /dev/null
+++ b/main/app/sprinkles/admin/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "userfrosting/sprinkle-admin",
+ "type": "userfrosting-sprinkle",
+ "description": "Administrative management module for UserFrosting.",
+ "keywords": ["php user management", "usercake", "bootstrap"],
+ "homepage": "https://github.com/userfrosting/UserFrosting",
+ "license" : "MIT",
+ "authors" : [
+ {
+ "name": "Alexander Weissman",
+ "homepage": "https://alexanderweissman.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "autoload": {
+ "psr-4": {
+ "UserFrosting\\Sprinkle\\Admin\\": "src/"
+ }
+ }
+}
diff --git a/main/app/sprinkles/admin/locale/ar/messages.php b/main/app/sprinkles/admin/locale/ar/messages.php
new file mode 100755
index 0000000..d4a3a44
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/ar/messages.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Modern Standard Arabic message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\ar
+ * @author Alexander Weissman and Abdullah Seba
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "نشاط",
+ 2 => "أنشطة",
+
+ "LAST" => "النشاط الاخير",
+ "PAGE" => "قائمة من أنشطة المستخدم",
+ "TIME" => "وقت نشاط"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "مسح ذاكرة التخزين",
+ "CLEAR_CONFIRM" => "هل أنت متأكد أنك تريد مسح ذاكرة التخزين بالموقع؟",
+ "CLEAR_CONFIRM_YES" => "نعم، إمسح ذاكرة التخزين",
+ "CLEARED" => "تم مسح ذاكرة التخزين بنجاح"
+ ],
+
+ "DASHBOARD" => "لوحة القيادة",
+ "NO_FEATURES_YET" => "لا يبدو أن أي ميزات تم إعدادها لهذا الحساب حتى الآن. ربما لم يتم تنفيذها بعد، أو ربما شخص نسي أن يعطيك الوصول. في كلتا الحالتين، نحن سعداء أن يكون لك على متن!",
+ "DELETE_MASTER" => "لا يمكنك حذف الحساب الرئيسي",
+ "DELETION_SUCCESSFUL" => "المستعمل <strong>{{user_name}}</strong> حذف بنجاح",
+ "DETAILS_UPDATED" => "جدد تفاصيل الحساب للمستخدم <strong>{{user_name}}</strong>",
+ "DISABLE_MASTER" => "لا يمكنك تعطيل الحساب الرئيسي",
+ "DISABLE_SUCCESSFUL" => "حساب المستخدم <strong>{{user_name}}</strong> عطيل بنجاح",
+
+ "ENABLE_SUCCESSFUL" => "حساب المستخدم <strong>{{user_name}}</strong> مكين بنجاح",
+
+ "GROUP" => [
+ 1 => "مجموعة",
+ 2 => "مجموعات",
+
+ "CREATE" => "إنشاء مجموعة",
+ "DELETE" => "حذف مجموعة",
+ "DELETE_CONFIRM" => "هل أنت متأكد أنك تريد حذف مجموعة <strong>{{name}}</strong>?",
+ "DELETE_YES" => "نعم، إحذف مجموعة",
+ "EDIT" => "تعديل مجموعة",
+ "ICON" => "رمز المجموعة",
+ "ICON_EXPLAIN" => "رمز المستخدمين في المجموعه",
+ "INFO_PAGE" => "صفحة معلومات المجموعة ل {{name}}",
+ //"MANAGE" => "Manage group",
+ "NAME" => "أسم المجموعة",
+ "NAME_EXPLAIN" => "ادخال اسم للمجموعة",
+ "PAGE_DESCRIPTION" => "قائمة المجموعات لموقعك يوفر أدوات لإدارة التحرير وحذف مجموعات"
+ ],
+
+ "MANUALLY_ACTIVATED" => "تم تفعيل حساب{{user_name}}",
+ "MASTER_ACCOUNT_EXISTS" => "الحساب الرئيسي موجود بالفعل",
+ "MIGRATION" => [
+ "REQUIRED" => "تحديث قاعدة البيانات مطلوب"
+ ],
+
+ "PERMISSION" => [
+ 1 => "الإذن",
+ 2 => "مأذونيات",
+
+ "ASSIGN_NEW" => "تعيين إذن جديد",
+ "HOOK_CONDITION" => "الظروف",
+ "MANAGE" => "إدارة المأذونات",
+ "PAGE_DESCRIPTION" => "قائمة المأذونات لموقعك",
+ "UPDATE" => "تحديث المأذونات"
+ ],
+
+ "ROLE" => [
+ 1 => "وظيفة",
+ 2 => "وظائف",
+
+ "ASSIGN_NEW" => "تعيين دور جديد",
+ "CREATE" => "إنشاء دور",
+ "DELETE" => "حذف دور",
+ "DELETE_CONFIRM" => "هل أنت متأكد أنك تريد حذف الدور <strong>{{name}}</strong>?",
+ "DELETE_YES" => "نعم، حذف دور",
+ "EDIT" => "إدارة دور",
+ "INFO_PAGE" => "صفحة معلومات دور {{name}}",
+ "MANAGE" => "إدارة الوظائف",
+ "NAME" => "اسم",
+ "NAME_EXPLAIN" => "أدخل اسما للدور",
+ "PAGE_DESCRIPTION" => "قائمة الوظائف لموقعك",
+ "UPDATED" => "تحديث الوظائف"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "معلومات الجهاز",
+
+ "DB_NAME" => "اسم قاعدة البيانات",
+ "DB_VERSION" => "إصدار قاعدة البيانات",
+ "DIRECTORY" => "دليل المشروع",
+ "PHP_VERSION" => "الإصدار PHP",
+ "SERVER" => "برنامج الخادم",
+ "SPRINKLES" => "sprinkles المحمل",
+ "UF_VERSION" => "إصدار UserFrosting",
+ "URL" => "رابط قاعدة الموقع"
+ ],
+
+ "USER" => [
+ 1 => "مستخدم",
+ 2 => "المستخدمين",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "تغيير كلمة المرور للمستخدم",
+ "SEND_PASSWORD_LINK" => "إرسال المستخدم وصلة من شأنها أن تسمح لهم لاختيار كلمة المرور الخاصة بهم",
+ "SET_PASSWORD" => "تعيين كلمة المرور الخاصة بالمستخدم"
+ ],
+
+ "ACTIVATE" => "تفعيل المستخدم",
+ "CREATE" => "إنشاء مستخدم",
+ "DELETE" => "مسح المستخدم",
+ "DELETE_CONFIRM" => "هل أنت متأكد أنك تريد حذف المستخدم <strong>{{name}}</strong>?",
+ "DELETE_YES" => "نعم، حذف المستخدم",
+ "DISABLE" => "تعطيل المستخدم ",
+ "EDIT" => "إدارة المستخدم",
+ "ENABLE" => "تمكين المستخدم",
+ "INFO_PAGE" => "صفحة معلومات المستخدم {{name}}",
+ "PAGE_DESCRIPTION" => "قائمة المستخدمين لموقعك",
+ "LATEST" => "أحدث المستخدمين",
+ "VIEW_ALL" => "عرض جميع المستخدمين"
+ ],
+ "X_USER" => [
+ 0 => "لا يوجد اي مستخدمين",
+ 1 => "{{plural}} مستخدم",
+ 2 => "{{plural}} المستخدمين"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/de_DE/messages.php b/main/app/sprinkles/admin/locale/de_DE/messages.php
new file mode 100755
index 0000000..6e21ab5
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/de_DE/messages.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * German message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\de
+ * @author X-Anonymous-Y
+ * @author kevinrombach
+ * @author splitt3r
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Aktivität",
+ 2 => "Aktivitäten",
+
+ "LAST" => "Letzte Aktivität",
+ "PAGE" => "Eine Auflistung der Benutzeraktivitäten",
+ "TIME" => "Aktivitätszeit"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Cache löschen",
+ "CLEAR_CONFIRM" => "Sind Sie sicher, dass Sie den Seiten-Cache löschen möchten?",
+ "CLEAR_CONFIRM_YES" => "Ja, Cache löschen",
+ "CLEARED" => "Cache wurde erfolgreich gelöscht!"
+ ],
+
+ "DASHBOARD" => "Übersicht",
+ "NO_FEATURES_YET" => "Es sieht aus, als wären für Ihren Account noch keine Funktionen aktiviert... bisher. Entweder sie wurden bisher noch nicht implementiert, oder Ihnen fehlen noch die Berechtigungen. Trotzdem ist es schön, dass Sie auf unsere Seite gekommen sind!",
+ "DELETE_MASTER" => "Sie können das Root-Konto nicht löschen!",
+ "DELETION_SUCCESSFUL" => "Benutzer <strong>{{user_name}}</strong> wurde erfolgreich gelöscht.",
+ "DETAILS_UPDATED" => "Konto-Daten für <strong>{{user_name}}</strong> aktualisiert.",
+ "DISABLE_MASTER" => "Sie können das Root-Konto nicht deaktivieren!",
+ "DISABLE_SELF" => "Sie können Ihr eigenes Konto nicht deaktivieren!",
+ "DISABLE_SUCCESSFUL" => "Konto von <strong>{{user_name}}</strong> wurde erfolgreich deaktiviert.",
+
+ "ENABLE_SUCCESSFUL" => "Konto von {{user_name}} wurde erfolgreich aktiviert.",
+
+ "GROUP" => [
+ 1 => "Gruppe",
+ 2 => "Gruppen",
+
+ "CREATE" => "Gruppe erstellen",
+ "CREATION_SUCCESSFUL" => "Die Gruppe <strong>{{name}}</strong> wurde erfolgreich erstellt",
+ "DELETE" => "Gruppe löschen",
+ "DELETE_CONFIRM" => "Möchten Sie die Gruppe <strong>{{name}}</strong> wirklich löschen?",
+ "DELETE_DEFAULT" => "Sie können die Gruppe <strong>{{name}}</strong> nicht löschen, da es die Standardgruppe für neu registrierte Benutzer ist.",
+ "DELETE_YES" => "Ja, Gruppe löschen",
+ "DELETION_SUCCESSFUL" => "Die Gruppe <strong>{{name}}</strong> wurde erfolgreich gelöscht",
+ "EDIT" => "Gruppe bearbeiten",
+ "ICON" => "Gruppensymbol",
+ "ICON_EXPLAIN" => "Symbol für Gruppenmitglieder",
+ "INFO_PAGE" => "Gruppeninformationsseite für {{name}}",
+ "MANAGE" => "Gruppe verwalten",
+ "NAME" => "Gruppenname",
+ "NAME_EXPLAIN" => "Geben Sie einen Namen für die Gruppe ein",
+ "NOT_EMPTY" => "Sie können das nicht tun, denn es sind noch Benutzer mit der Gruppe <strong>{{name}}</strong> verbunden.",
+ "PAGE_DESCRIPTION" => "Eine Liste der Gruppen für Ihre Website. Bietet Verwaltungstools für das Bearbeiten und Löschen von Gruppen.",
+ "SUMMARY" => "Gruppen Zusammenfassung",
+ "UPDATE" => "Details für die Gruppe <strong>{{name}}</strong> aktualisiert"
+ ],
+
+ "MANUALLY_ACTIVATED" => "{{user_name}}'s Konto wurde manuell aktiviert.",
+ "MASTER_ACCOUNT_EXISTS" => "Das Root-Konto existiert bereits!",
+ "MIGRATION" => [
+ "REQUIRED" => "Datenbankaktualisierung erforderlich"
+ ],
+
+ "PERMISSION" => [
+ 1 => "Berechtigung",
+ 2 => "Berechtigungen",
+
+ "ASSIGN_NEW" => "Neue Berechtigung zuweisen",
+ "HOOK_CONDITION" => "Haken/Bedingungen",
+ "ID" => "Berechtigungs-ID",
+ "INFO_PAGE" => "Berechtigungs Informationen für '{{name}}'",
+ "MANAGE" => "Berechtigungen verwalten",
+ "NOTE_READ_ONLY" => "<strong>Bitte beachten Sie:</strong> Berechtigungen werden als \"Teil des Quelltexts\" gesehen und können hier nicht bearbeitet werden. Um Berechtigungen hinzuzufügen, zu bearbeiten, oder zu löschen, benutzen Sie bitte folgende Dokumentation zur <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about:_blank\">Datenbank Migration.</a>",
+ "PAGE_DESCRIPTION" => "Eine Liste der Berechtigungen für Ihre Website. Bietet Verwaltungstools zum Bearbeiten und Löschen von Berechtigungen.",
+ "SUMMARY" => "Berechtigungs Zusammenfassung",
+ "UPDATE" => "Berechtigungen aktualisieren",
+ "VIA_ROLES" => "Besitzt die Berechtigung durch die Rolle"
+ ],
+
+ "ROLE" => [
+ 1 => "Rolle",
+ 2 => "Rollen",
+
+ "ASSIGN_NEW" => "Neue Rolle zuweisen",
+ "CREATE" => "Rolle erstellen",
+ "CREATION_SUCCESSFUL" => "Die Rolle <strong>{{name}}</strong> wurde erfolgreich erstellt",
+ "DELETE" => "Rolle löschen",
+ "DELETE_CONFIRM" => "Sind Sie sicher, dass Sie die Rolle <strong>{{name}}</strong> löschen möchten?",
+ "DELETE_DEFAULT" => "Sie können die Rolle <strong>{{name}}</strong> nicht löschen, da es eine Standardrolle für neu registrierte Benutzer ist.",
+ "DELETE_YES" => "Ja, Rolle löschen",
+ "DELETION_SUCCESSFUL" => "Die Rolle <strong>{{name}}</strong> wurde erfolgreich gelöscht",
+ "EDIT" => "Rolle bearbeiten",
+ "HAS_USERS" => "Sie können das nicht machen weil es noch Benutzer gibt, die die Rolle <strong>{{name}}</strong> haben.",
+ "INFO_PAGE" => "Rolleninformationsseite für {{name}}",
+ "MANAGE" => "Rollen verwalten",
+ "NAME" => "Name",
+ "NAME_EXPLAIN" => "Geben Sie einen Namen für die Rolle ein",
+ "NAME_IN_USE" => "Eine Rolle mit dem Namen <strong>{{name}}</strong> existiert bereits",
+ "PAGE_DESCRIPTION" => "Eine Liste der Rollen für Ihre Website. Bietet Verwaltungstools zum Bearbeiten und Löschen von Rollen.",
+ "PERMISSIONS_UPDATED" => "Berechtigungen für die Rolle <strong>{{name}}</strong> aktualisiert",
+ "SUMMARY" => "Rollen Zusammenfassung",
+ "UPDATED" => "Rollen aktualisieren"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "System Information",
+
+ "DB_NAME" => "Name der Datenbank",
+ "DB_VERSION" => "Datenbankversion",
+ "DIRECTORY" => "Projektverzeichnis",
+ "PHP_VERSION" => "PHP-Version",
+ "SERVER" => "Web-Server-Software",
+ "SPRINKLES" => "Geladene Sprinkles",
+ "UF_VERSION" => "UserFrosting Version",
+ "URL" => "Website-Stamm-Url"
+ ],
+
+ "TOGGLE_COLUMNS" => "Spalten anpassen",
+
+ "USER" => [
+ 1 => "Benutzer",
+ 2 => "Benutzer",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Benutzerpasswort ändern",
+ "SEND_PASSWORD_LINK" => "Senden Sie dem Benutzer einen Link, der ihnen erlaubt, ihr eigenes Passwort zu wählen",
+ "SET_PASSWORD" => "Setzen Sie das Passwort des Benutzers als"
+ ],
+
+ "ACTIVATE" => "Benutzer aktivieren",
+ "CREATE" => "Benutzer erstellen",
+ "CREATED" => "Benutzer <strong>{{user_name}}</strong> wurde erfolgreich erstellt",
+ "DELETE" => "Benutzer löschen",
+ "DELETE_CONFIRM" => "Sind Sie sicher, dass Sie den Benutzer <strong>{{name}}</strong> löschen möchten?",
+ "DELETE_YES" => "Ja, Benutzer löschen",
+ "DISABLE" => "Benutzer deaktivieren",
+ "EDIT" => "Benutzer bearbeiten",
+ "ENABLE" => "Benutzer aktivieren",
+ "INFO_PAGE" => "Benutzerinformationsseite für {{name}}",
+ "LATEST" => "Neueste Benutzer",
+ "PAGE_DESCRIPTION" => "Eine Liste der Benutzer für Ihre Website. Bietet Management-Tools, einschließlich der Möglichkeit, Benutzerdaten bearbeiten, manuell aktivieren, Benutzer aktivieren/deaktivieren, und vieles mehr.",
+ "SUMMARY" => "Benutzer Zusammenfassung",
+ "VIEW_ALL" => "Alle Benutzer anzeigen",
+ "WITH_PERMISSION" => "Benutzer mit dieser Berechtigung"
+ ],
+ "X_USER" => [
+ 0 => "Keine Benutzer",
+ 1 => "{{plural}} Benutzer",
+ 2 => "{{plural}} Benutzer"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/en_US/messages.php b/main/app/sprinkles/admin/locale/en_US/messages.php
new file mode 100755
index 0000000..a21e325
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/en_US/messages.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * US English message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\en_US
+ * @author Alexander Weissman
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Activity",
+ 2 => "Activities",
+
+ "LAST" => "Last Activity",
+ "PAGE" => "A listing of user activities",
+ "TIME" => "Activity Time"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Clear cache",
+ "CLEAR_CONFIRM" => "Are you sure you want to clear the site cache?",
+ "CLEAR_CONFIRM_YES" => "Yes, clear cache",
+ "CLEARED" => "Cache cleared successfully !"
+ ],
+
+ "DASHBOARD" => "Dashboard",
+ "NO_FEATURES_YET" => "It doesn't look like any features have been set up for this account...yet. Maybe they haven't been implemented yet, or maybe someone forgot to give you access. Either way, we're glad to have you aboard!",
+ "DELETE_MASTER" => "You cannot delete the master account!",
+ "DELETION_SUCCESSFUL" => "User <strong>{{user_name}}</strong> has been successfully deleted.",
+ "DETAILS_UPDATED" => "Account details updated for user <strong>{{user_name}}</strong>",
+ "DISABLE_MASTER" => "You cannot disable the master account!",
+ "DISABLE_SELF" => "You cannot disable your own account!",
+ "DISABLE_SUCCESSFUL" => "Account for user <strong>{{user_name}}</strong> has been successfully disabled.",
+
+ "ENABLE_SUCCESSFUL" => "Account for user <strong>{{user_name}}</strong> has been successfully enabled.",
+
+ "GROUP" => [
+ 1 => "Group",
+ 2 => "Groups",
+
+ "CREATE" => "Create group",
+ "CREATION_SUCCESSFUL" => "Successfully created group <strong>{{name}}</strong>",
+ "DELETE" => "Delete group",
+ "DELETE_CONFIRM" => "Are you sure you want to delete the group <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "You can't delete the group <strong>{{name}}</strong> because it is the default group for newly registered users.",
+ "DELETE_YES" => "Yes, delete group",
+ "DELETION_SUCCESSFUL" => "Successfully deleted group <strong>{{name}}</strong>",
+ "EDIT" => "Edit group",
+ "ICON" => "Group icon",
+ "ICON_EXPLAIN" => "Icon for group members",
+ "INFO_PAGE" => "Group information page for {{name}}",
+ "MANAGE" => "Manage group",
+ "NAME" => "Group name",
+ "NAME_EXPLAIN" => "Please enter a name for the group",
+ "NOT_EMPTY" => "You can't do that because there are still users associated with the group <strong>{{name}}</strong>.",
+ "PAGE_DESCRIPTION" => "A listing of the groups for your site. Provides management tools for editing and deleting groups.",
+ "SUMMARY" => "Group Summary",
+ "UPDATE" => "Details updated for group <strong>{{name}}</strong>"
+ ],
+
+ "MANUALLY_ACTIVATED" => "{{user_name}}'s account has been manually activated",
+ "MASTER_ACCOUNT_EXISTS" => "The master account already exists!",
+ "MIGRATION" => [
+ "REQUIRED" => "Database update required"
+ ],
+
+ "PERMISSION" => [
+ 1 => "Permission",
+ 2 => "Permissions",
+
+ "ASSIGN_NEW" => "Assign new permission",
+ "HOOK_CONDITION" => "Hook/Conditions",
+ "ID" => "Permission ID",
+ "INFO_PAGE" => "Permission information page for '{{name}}'",
+ "MANAGE" => "Manage permissions",
+ "NOTE_READ_ONLY" => "<strong>Please note:</strong> permissions are considered \"part of the code\" and cannot be modified through the interface. To add, remove, or modify permissions, the site maintainers will need to use a <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about:_blank\">database migration.</a>",
+ "PAGE_DESCRIPTION" => "A listing of the permissions for your site. Provides management tools for editing and deleting permissions.",
+ "SUMMARY" => "Permission Summary",
+ "UPDATE" => "Update permissions",
+ "VIA_ROLES" => "Has permission via roles"
+ ],
+
+ "ROLE" => [
+ 1 => "Role",
+ 2 => "Roles",
+
+ "ASSIGN_NEW" => "Assign new role",
+ "CREATE" => "Create role",
+ "CREATION_SUCCESSFUL" => "Successfully created role <strong>{{name}}</strong>",
+ "DELETE" => "Delete role",
+ "DELETE_CONFIRM" => "Are you sure you want to delete the role <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "You can't delete the role <strong>{{name}}</strong> because it is a default role for newly registered users.",
+ "DELETE_YES" => "Yes, delete role",
+ "DELETION_SUCCESSFUL" => "Successfully deleted role <strong>{{name}}</strong>",
+ "EDIT" => "Edit role",
+ "HAS_USERS" => "You can't do that because there are still users who have the role <strong>{{name}}</strong>.",
+ "INFO_PAGE" => "Role information page for {{name}}",
+ "MANAGE" => "Manage Roles",
+ "NAME" => "Name",
+ "NAME_EXPLAIN" => "Please enter a name for the role",
+ "NAME_IN_USE" => "A role named <strong>{{name}}</strong> already exist",
+ "PAGE_DESCRIPTION" => "A listing of the roles for your site. Provides management tools for editing and deleting roles.",
+ "PERMISSIONS_UPDATED" => "Permissions updated for role <strong>{{name}}</strong>",
+ "SUMMARY" => "Role Summary",
+ "UPDATED" => "Details updated for role <strong>{{name}}</strong>"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "System information",
+
+ "DB_NAME" => "Database name",
+ "DB_VERSION" => "Database version",
+ "DIRECTORY" => "Project directory",
+ "PHP_VERSION" => "PHP version",
+ "SERVER" => "Webserver software",
+ "SPRINKLES" => "Loaded sprinkles",
+ "UF_VERSION" => "UserFrosting version",
+ "URL" => "Site root url"
+ ],
+
+ "TOGGLE_COLUMNS" => "Toggle columns",
+
+ "USER" => [
+ 1 => "User",
+ 2 => "Users",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Change User Password",
+ "SEND_PASSWORD_LINK" => "Send the user a link that will allow them to choose their own password",
+ "SET_PASSWORD" => "Set the user's password as"
+ ],
+
+ "ACTIVATE" => "Activate user",
+ "CREATE" => "Create user",
+ "CREATED" => "User <strong>{{user_name}}</strong> has been successfully created",
+ "DELETE" => "Delete user",
+ "DELETE_CONFIRM" => "Are you sure you want to delete the user <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Yes, delete user",
+ "DELETED" => "User deleted",
+ "DISABLE" => "Disable user",
+ "EDIT" => "Edit user",
+ "ENABLE" => "Enable user",
+ "INFO_PAGE" => "User information page for {{name}}",
+ "LATEST" => "Latest Users",
+ "PAGE_DESCRIPTION" => "A listing of the users for your site. Provides management tools including the ability to edit user details, manually activate users, enable/disable users, and more.",
+ "SUMMARY" => "Account Summary",
+ "VIEW_ALL" => "View all users",
+ "WITH_PERMISSION" => "Users with this permission"
+ ],
+ "X_USER" => [
+ 0 => "No users",
+ 1 => "{{plural}} user",
+ 2 => "{{plural}} users"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/es_ES/messages.php b/main/app/sprinkles/admin/locale/es_ES/messages.php
new file mode 100755
index 0000000..a8950c0
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/es_ES/messages.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Spanish message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\es_ES
+ * @author rafa31gz
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Actividad",
+ 2 => "Actividades",
+
+ "LAST" => "Última actividad",
+ "PAGE" => "Una lista de las actividades del usuario",
+ "TIME" => "Tiempo de Actividad"
+ ],
+
+ "ADMIN" => [
+ "PANEL" => "Panel de administración"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Limpiar cache",
+ "CLEAR_CONFIRM" => "¿Está seguro de que desea borrar la caché del sitio?",
+ "CLEAR_CONFIRM_YES" => "Sí, borrar caché",
+ "CLEARED" => "¡Cache borrado correctamente!"
+ ],
+
+ "DASHBOARD" => "Tablero",
+ "NO_FEATURES_YET" => "No parece que se hayan configurado funciones para esta cuenta ... todavía. Tal vez no se han implementado todavía, o tal vez alguien se olvidó de darle acceso. De cualquier manera, ¡estamos encantados de tenerte a bordo!",
+ "DELETE_MASTER" => "¡No puede eliminar la cuenta principal!",
+ "DELETION_SUCCESSFUL" => "El usuario <strong> {{user_name}} </strong> se ha eliminado correctamente.",
+ "DETAILS_UPDATED" => "Detalles de la cuenta actualizados para el usuario <strong> {{user_name}} </strong>",
+ "DISABLE_MASTER" => "¡No puedes deshabilitar la cuenta principal!",
+ "DISABLE_SELF" => "¡No puedes inhabilitar tu propia cuenta!",
+ "DISABLE_SUCCESSFUL" => "La cuenta para el usuario <strong> {{user_name}} </strong> se ha desactivado correctamente.",
+
+ "ENABLE_SUCCESSFUL" => "La cuenta para el usuario <strong> {{user_name}} </strong> se ha habilitado correctamente.",
+
+ "GROUP" => [
+ 1 => "Grupo",
+ 2 => "Grupos",
+
+ "CREATE" => "Crea un grupo",
+ "CREATION_SUCCESSFUL" => "Grupo creado correctamente <strong> {{name}} </strong>",
+ "DELETE" => "Delete group",
+ "DELETE_CONFIRM" => "¿Seguro que quieres eliminar el grupo <strong> {{name}} </strong>?",
+ "DELETE_DEFAULT" => "No puedes eliminar el grupo <strong> {{name}} </strong> porque es el grupo predeterminado para los usuarios recién registrados.",
+ "DELETE_YES" => "Sí, eliminar grupo",
+ "DELETION_SUCCESSFUL" => "Grupo eliminado correctamente <strong> {{name}} </strong>",
+ "EDIT" => "Editar grupo",
+ "ICON" => "Icono de grupo",
+ "ICON_EXPLAIN" => "Icono para los miembros del grupo",
+ "INFO_PAGE" => "Página de información de grupo para {{name}}",
+ "MANAGE" => "Administrar grupo",
+ "NAME" => "Nombre del grupo",
+ "NAME_EXPLAIN" => "Introduzca un nombre para el grupo",
+ "NOT_EMPTY" => "No puedes hacerlo porque todavía hay usuarios asociados con el grupo <strong> {{name}} </strong>.",
+ "PAGE_DESCRIPTION" => "Un listado de los grupos para su sitio. Proporciona herramientas de administración para editar y eliminar grupos.",
+ "SUMMARY" => "Resumen del grupo",
+ "UPDATE" => "Detalles actualizados para el grupo <strong> {{name}} </strong>"
+ ],
+
+ "MANUALLY_ACTIVATED" => "La cuenta de {{user_name}} se ha activado manualmente",
+ "MASTER_ACCOUNT_EXISTS" => "¡La cuenta maestra ya existe!",
+ "MIGRATION" => [
+ "REQUIRED" => "Se requiere actualizar la base de datos"
+ ],
+
+ "PERMISSION" => [
+ 1 => "Permiso",
+ 2 => "Permisos",
+
+ "ASSIGN_NEW" => "Asignar nuevo permiso",
+ "HOOK_CONDITION" => "Hook/Condiciones",
+ "ID" => "ID de permiso",
+ "INFO_PAGE" => "Página de autor del permiso de '{{name}}'",
+ "MANAGE" => "Administrar permisos",
+ "NOTE_READ_ONLY" => "<strong> Tenga en cuenta: </strong> los permisos se consideran \"parte del código\" y no se pueden modificar a través de la interfaz. Para agregar, eliminar o modificar permisos, los mantenedores del sitio necesitarán usar una <a href=\"https://learn.userfrosting.com/database/extended-the-database\" target=\"about:_blank\"> migración de la base de datos . </a>",
+ "PAGE_DESCRIPTION" => "Una lista de los permisos para su sitio. Proporciona herramientas de administración para editar y eliminar permisos.",
+ "SUMMARY" => "Resumen del permiso",
+ "UPDATE" => "Actualizar permisos",
+ "VIA_ROLES" => "Tiene permiso para los roles"
+ ],
+
+ "ROLE" => [
+ 1 => "Rol(funcion)",
+ 2 => "Roles(funciones)",
+
+ "ASSIGN_NEW" => "Asignar nueva rol",
+ "CREATE" => "Crear un rol",
+ "CREATION_SUCCESSFUL" => "Función creada correctamente <strong> {{name}} </strong>",
+ "DELETE" => "Eliminar rol",
+ "DELETE_CONFIRM" => "¿Seguro que quieres eliminar la función <strong> {{name}} </strong>?",
+ "DELETE_DEFAULT" => "No puedes eliminar el rol <strong> {{name}} </strong> porque es un rol predeterminado para los usuarios recién registrados.",
+ "DELETE_YES" => "Sí, borrar función",
+ "DELETION_SUCCESSFUL" => "Se ha eliminado la función <strong> {{nombre}} </strong>",
+ "EDIT" => "Editar función",
+ "HAS_USERS" => "No puedes hacerlo porque todavía hay usuarios que tienen el rol <strong> {{name}} </strong>.",
+ "INFO_PAGE" => "Página de información de funciones de {{name}}",
+ "MANAGE" => "Administrar roles",
+ "NAME" => "Nombre",
+ "NAME_EXPLAIN" => "Ingrese un nombre para el rol",
+ "NAME_IN_USE" => "Ya existe un rol denominado <strong> {{name}} </strong>",
+ "PAGE_DESCRIPTION" => "Una lista de las funciones de su sitio. Proporciona herramientas de administración para editar y eliminar roles.",
+ "PERMISSIONS_UPDATED" => "Permisos actualizados para el rol <strong> {{name}} </strong>",
+ "SUMMARY" => "Resumen del rol",
+ "UPDATED" => "Detalles actualizados para el rol <strong> {{name}} </strong>"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "Información del sistema",
+
+ "DB_NAME" => "Nombre de la base de datos",
+ "DB_VERSION" => "Versión de base de datos",
+ "DIRECTORY" => "Directorio del proyecto",
+ "PHP_VERSION" => "Versión de PHP",
+ "SERVER" => "Software de servidor Web",
+ "SPRINKLES" => "Sprinkles cargados",
+ "UF_VERSION" => "UserFrosting versión",
+ "URL" => "URL root del sitio"
+ ],
+
+ "TOGGLE_COLUMNS" => "Alternar columnas",
+ "NO_DATA" => "No puede quedar vacio.",
+
+ "USER" => [
+ 1 => "Usuario",
+ 2 => "Usuarios",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Cambiar contraseña de usuario",
+ "SEND_PASSWORD_LINK" => "Enviar al usuario un enlace que les permita elegir su propia contraseña",
+ "SET_PASSWORD" => "Establezca la contraseña del usuario como"
+ ],
+
+ "ACTIVATE" => "Activar usuario",
+ "CREATE" => "Crear usuario",
+ "CREATED" => "Se ha creado correctamente el usuario <strong> {{user_name}} </strong>",
+ "DELETE" => "Borrar usuario",
+ "DELETE_CONFIRM" => "¿Seguro que desea eliminar el usuario <strong> {{name}} </strong>?",
+ "DELETE_YES" => "Sí, eliminar usuario",
+ "DISABLE" => "Deshabilitar usuario",
+ "EDIT" => "Editar usuario",
+ "ENABLE" => "Habilitar usuario",
+ "INFO_PAGE" => "Página de información de usuario de {{name}}",
+ "LATEST" => "Usuarios más recientes",
+ "PAGE_DESCRIPTION" => "Una lista de los usuarios para su sitio. Proporciona herramientas de administración que incluyen la capacidad de editar detalles de usuario, activar manualmente usuarios, habilitar / deshabilitar usuarios y más.",
+ "SUMMARY" => "Resumen de la cuenta",
+ "VIEW_ALL" => "Ver todos los usuarios",
+ "WITH_PERMISSION" => "Usuarios con este permiso"
+ ],
+ "X_USER" => [
+ 0 => "No hay usuarios",
+ 1 => "{{plural}} usuario",
+ 2 => "{{plural}} usuarios"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/fa/messages.php b/main/app/sprinkles/admin/locale/fa/messages.php
new file mode 100755
index 0000000..75a8dee
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/fa/messages.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Standard Farsi/Persian message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\fa
+ * @author aminakbari
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "فعالیت",
+ 2 => "فعالیت ها",
+
+ "LAST" => "آخرین فعالیت",
+ "PAGE" => "لیستی از فعالیت های کاربر",
+ "TIME" => "زمان فعالیت"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "پاک سازی کش",
+ "CLEAR_CONFIRM" => "آیا مطمئن هستید که میخواهید کش سایت را پاک سازی کنید؟",
+ "CLEAR_CONFIRM_YES" => "بله، کش پاک سازی شود",
+ "CLEARED" => "کش با موفقیت پاک سازی شد"
+ ],
+
+ "DASHBOARD" => "کارتابل",
+ "DELETE_MASTER" => "شما نمیتوانید کاربر اصلی را حذف کنید",
+ "DELETION_SUCCESSFUL" => "<strong>{{user_name}}</strong> با موفقیت حذف شد.",
+ "DETAILS_UPDATED" => "جزئیات <strong>{{user_name}}</strong> با موفقیت ذخیره شد.",
+ "DISABLE_MASTER" => "شما نمیتوانید کاربر اصلی را غیر فعال کنید.",
+ "DISABLE_SUCCESSFUL" => "حساب کاربری <strong>{{user_name}}</strong> با موفقیت غیر فعال شد.",
+
+ "ENABLE_SUCCESSFUL" => "حساب کاربری <strong>{{user_name}}</strong> با موفقیت فعال شد.",
+
+ "GROUP" => [
+ 1 => "گروه",
+ 2 => "گروه ها",
+
+ "CREATE" => "اضافه کردن گروه",
+ "CREATION_SUCCESSFUL" => "گروه <strong>{{name}}</strong> با موفقیت اضافه شد",
+ "DELETE" => "حذف گروه",
+ "DELETE_CONFIRM" => "آیا مطمئن هستید که میخواهید گروه <strong>{{name}}</strong> را حذف کنید؟",
+ "DELETE_DEFAULT" => "شما نمیتوانید گروه <strong>{{name}}</strong> را حذف کنید چون به عنوان گروه پیش فرض برای کاربران جدید انتخاب شده است.",
+ "DELETE_YES" => "بله، گروه حذف شود",
+ "DELETION_SUCCESSFUL" => "گروه <strong>{{name}}</strong> با موفقیت حذف شد.",
+ "EDIT" => "ویرایش گروه",
+ "ICON" => "آیکن گروه",
+ "ICON_EXPLAIN" => "آیکن برای اعضای گروه",
+ "INFO_PAGE" => "صفحه توضیحات گروه برای {{name}}",
+ "MANAGE" => "مدیریت گروه",
+ "NAME" => "نام گروه",
+ "NAME_EXPLAIN" => "لطفا نام گروه را وارد کنید",
+ "NOT_EMPTY" => "نمیتوان این کار را کرد چون هنوز کاربرانی عضو گروه <strong>{{name}}</strong> هستند.",
+ "PAGE_DESCRIPTION" => "لیست گروه های وب سایت شما. امکان مدیریت این گروه ها در این صفحه وجود دارد.",
+ "SUMMARY" => "توضیحات گروه",
+ "UPDATE" => "اطلاعات گروه <strong>{{name}}</strong> به روز رسانی شد."
+ ],
+
+ "MANUALLY_ACTIVATED" => "حساب کاربری {{user_name}} بصورت دستی فعال شد.",
+ "MASTER_ACCOUNT_EXISTS" => "حساب کاربری اصلی وجود دارد!",
+ "MIGRATION" => [
+ "REQUIRED" => "به روز رسانی پایگاه داده ها باید انجام شود"
+ ],
+
+ "PERMISSION" => [
+ 1 => "دسترسی",
+ 2 => "دسترسی ها",
+
+ "ASSIGN_NEW" => "دادن دسترسی",
+ "HOOK_CONDITION" => "قلاب/شرط",
+ "ID" => "آی دی دسترسی",
+ "INFO_PAGE" => "توضیحات دسترسی {{name}}",
+ "MANAGE" => "مدیریت دسترسی ها",
+ "NOTE_READ_ONLY" => "<strong>توجه بفرمایید</strong>دسترسی ها بخشی از کد میباشند و آن ها را نمیتوان از اینترفیس تغییر داد. برای این تغییرات، مبایستی که مدیر، از <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about:_blank\">دیتابیس مایگریشن</a> استفاده کند. ",
+ "PAGE_DESCRIPTION" => "لیست دسترسی های وب سایت شما. امکان مدیریت این دسترسی ها در این صفحه وجود دارد.",
+ "SUMMARY" => "توضیحات دسترسی ها",
+ "UPDATE" => "به روز رسانی دسترسی ها",
+ "VIA_ROLES" => "از طریق وظیفه ها دسترسی دارد"
+ ],
+
+ "ROLE" => [
+ 1 => "وظیفه",
+ 2 => "وظیفه ها",
+
+ "ASSIGN_NEW" => "دادن وظیفه",
+ "CREATE" => "ساخت وظیفه",
+ "CREATION_SUCCESSFUL" => "وظیفه <strong>{{name}}</strong> با موفقیت ساخته شد",
+ "DELETE" => "حذف وظیفه",
+ "DELETE_CONFIRM" => "اطمینان دارید که میخواهید وظیفه <strong>{{name}}</strong> را حذف کنید؟",
+ "DELETE_DEFAULT" => "شما نمیتوانید وظیفه <strong>{{name}}</strong> را حذف کنید زیرا کاربرانی که تازه ثبت نام کنند، این وظیفه را دریافت خواهند کرد.",
+ "DELETE_YES" => "بله، وظیفه حذف شود",
+ "DELETION_SUCCESSFUL" => "وظیفه <strong>{{name}}</strong> با موفقیت حذف شد",
+ "EDIT" => "ویرایش وظیفه",
+ "HAS_USERS" => "نمیتوانید این کار را انجام دهید زیرا کاربرانی وظیفه <strong>{{name}}</strong> را هنوز دارند.",
+ "INFO_PAGE" => "صفحه توضیحات وظیفه {{name}}",
+ "MANAGE" => "مدیریت وظیفه ها",
+ "NAME" => "نام",
+ "NAME_EXPLAIN" => "لطفا برای وظیفه نامی انتخاب کنید",
+ "NAME_IN_USE" => "وظیفه ای با نام <strong>{{name}}</strong> موجود است",
+ "PAGE_DESCRIPTION" => "لیست وظیفه های وب سایت شما. امکان مدیریت این وظیفه ها در این صفحه وجود دارد.",
+ "PERMISSIONS_UPDATED" => "دسترسی ها برای وظیفه <strong>{{name}}</strong> به روز رسانی شد",
+ "SUMMARY" => "خلاصه وظیفه",
+ "UPDATED" => "اطلاعات وظیفه <strong>{{name}}</strong> به روز رسانی شد"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "توضیحات سیستم",
+
+ "DB_NAME" => "نام پایگاه داده",
+ "DB_VERSION" => "نسخه پایگاه داده",
+ "DIRECTORY" => "دایرکتوری پروژه",
+ "PHP_VERSION" => "نسخه پی اچ پی",
+ "SERVER" => "نرمافزار وب سرور",
+ "SPRINKLES" => "اسپرینکل های بارگذاری شده",
+ "UF_VERSION" => "نسخه یوزرفروستینگ",
+ "URL" => "آدرس رووت وب سایت"
+ ],
+
+ "TOGGLE_COLUMNS" => "تغییر ستون",
+
+ "USER" => [
+ 1 => "کاربر",
+ 2 => "کاربران",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "تغییر گذرواژه کاربر",
+ "SEND_PASSWORD_LINK" => "برای کاربر ایمیلی ارسال شود تا گذرواژه خود را تغییر دهد",
+ "SET_PASSWORD" => "گذرواژه کاربر را انتخاب کنید"
+ ],
+
+ "ACTIVATE" => "کاربر فعال",
+ "CREATE" => "اضافه کردن کاربر",
+ "CREATED" => "کاربر <strong>{{user_name}}</strong> با موفقیت اضافه شد",
+ "DELETE" => "حذف کاربر",
+ "DELETE_CONFIRM" => "آیا اطمینان دارید که میخواهید کاربر <strong>{{name}}</strong> را حذف کنید؟",
+ "DELETE_YES" => "بله، کاربر حذف شود",
+ "DISABLE" => "غیر فعال سازی کاربر",
+ "EDIT" => "ویرایش کاربر",
+ "ENABLE" => "فعال سازی کاربر",
+ "INFO_PAGE" => "صفحه توضیحات کاربر {{name}}",
+ "LATEST" => "آخرین کاربران",
+ "PAGE_DESCRIPTION" => "لیستی از کاربران سایت. این صفحه به شما امکان ویرایش، فعال سازی و غیر فعال سازی کاربران را می دهد.",
+ "SUMMARY" => "خلاصه حساب",
+ "VIEW_ALL" => "تماشای همه ی کاربران",
+ "WITH_PERMISSION" => "کاربرانی که این دسترسی را دارند"
+ ],
+ "X_USER" => [
+ 0 => "هیچ کاربری",
+ 1 => "{{plural}} کاربر",
+ 2 => "{{plural}} کاربر"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/fr_FR/messages.php b/main/app/sprinkles/admin/locale/fr_FR/messages.php
new file mode 100755
index 0000000..82bdf3e
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/fr_FR/messages.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * French message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\fr
+ * @author Louis Charette
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Activité",
+ 2 => "Activités",
+
+ "LAST" => "Dernière activité",
+ "PAGE" => "Une liste des activités des utilisateurs",
+ "TIME" => "Date de l'activité"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Vider le cache",
+ "CLEAR_CONFIRM" => "Voulez-vous vraiment supprimer le cache du site?",
+ "CLEAR_CONFIRM_YES" => "Oui, vider le cache",
+ "CLEARED" => "Cache effacé avec succès !"
+ ],
+
+ "DASHBOARD" => "Tableau de bord",
+ "DELETE_MASTER" => "Vous ne pouvez pas supprimer le compte principal !",
+ "DELETION_SUCCESSFUL" => "L'utilisateur <strong>{{user_name}}</strong> a été supprimé avec succès.",
+ "DETAILS_UPDATED" => "Les détails du compte de <strong>{{user_name}}</strong> ont été mis à jour",
+ "DISABLE_MASTER" => "Vous ne pouvez pas désactiver le compte principal !",
+ "DISABLE_SELF" => "Vous ne pouvez pas désactiver votre propre compte !",
+ "DISABLE_SUCCESSFUL" => "Le compte de l'utilisateur <strong>{{user_name}}</strong> a été désactivé avec succès.",
+
+ "ENABLE_SUCCESSFUL" => "Le compte de l'utilisateur <strong>{{user_name}}</strong> a été activé avec succès.",
+
+ "GROUP" => [
+ 1 => "Groupe",
+ 2 => "Groupes",
+
+ "CREATE" => "Créer un groupe",
+ "CREATION_SUCCESSFUL" => "Successfully created group <strong>{{name}}</strong>",
+ "DELETE" => "Supprimer le groupe",
+ "DELETE_CONFIRM" => "Êtes-vous certain de vouloir supprimer le groupe <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Vous ne pouvez pas supprimer le groupe <strong>{{name}}</strong> parce que c'est le groupe par défaut pour les utilisateurs nouvellement enregistrés.",
+ "DELETE_YES" => "Oui, supprimer le groupe",
+ "DELETION_SUCCESSFUL" => "Groupe <strong>{{name}}</strong> supprimé avec succès",
+ "EDIT" => "Modifier le groupe",
+ "ICON" => "Icône",
+ "ICON_EXPLAIN" => "Icône des membres du groupe",
+ "INFO_PAGE" => "Informations sur le groupe {{name}}",
+ "MANAGE" => "Gérer le groupe",
+ "NAME" => "Nom du groupe",
+ "NAME_EXPLAIN" => "Spécifiez le nom du groupe",
+ "NOT_EMPTY" => "Vous ne pouvez pas le faire car il y a encore des utilisateurs associés au groupe <strong>{{name}}</strong>.",
+ "PAGE_DESCRIPTION" => "Une liste des groupes pour votre site. Fournit des outils de gestion pour éditer et supprimer des groupes.",
+ "UPDATE" => "Les détails du groupe <strong>{{name}}</strong> ont été enregistrés"
+ ],
+
+ "MANUALLY_ACTIVATED" => "Le compte de {{user_name}} a été activé manuellement",
+ "MASTER_ACCOUNT_EXISTS" => "Le compte principal existe déjà !",
+ "MIGRATION" => [
+ "REQUIRED" => "Mise à jour de la base de données requise"
+ ],
+
+ "PERMISSION" => [
+ 1 => "Autorisation",
+ 2 => "Autorisations",
+
+ "ASSIGN_NEW" => "Assigner une nouvelle autorisation",
+ "HOOK_CONDITION" => "Hook/Conditions",
+ "MANAGE" => "Gestion des autorisations",
+ "PAGE_DESCRIPTION" => "Une liste des autorisations pour votre site. Fournit des outils de gestion pour modifier et supprimer des autorisations.",
+ "UPDATE" => "Mettre à jour les autorisations"
+ ],
+
+ "ROLE" => [
+ 1 => "Rôle",
+ 2 => "Rôles",
+
+ "ASSIGN_NEW" => "Assigner un nouveau rôle",
+ "CREATE" => "Créer un rôle",
+ "CREATION_SUCCESSFUL" => "Rôle <strong>{{name}}</strong> créé avec succès",
+ "DELETE" => "Supprimer le rôle",
+ "DELETE_CONFIRM" => "Êtes-vous certain de vouloir supprimer le rôle <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Vous ne pouvez pas supprimer le rôle <strong>{{name}}</strong> parce que c'est un rôle par défaut pour les utilisateurs nouvellement enregistrés.",
+ "DELETE_YES" => "Oui, supprimer le rôle",
+ "DELETION_SUCCESSFUL" => "Rôle <strong>{{name}}</strong> supprimé avec succès",
+ "EDIT" => "Modifier le rôle",
+ "HAS_USERS" => "Vous ne pouvez pas le faire parce qu'il y a encore des utilisateurs qui ont le rôle <strong>{{name}}</strong>.",
+ "INFO_PAGE" => "Page d'information pour le rôle {{name}}",
+ "MANAGE" => "Gérer les rôles",
+ "NAME" => "Nom du rôle",
+ "NAME_EXPLAIN" => "Spécifiez le nom du rôle",
+ "NAME_IN_USE" => "Un rôle nommé <strong>{{name}}</strong> existe déjà",
+ "PAGE_DESCRIPTION" => "Une liste des rôles de votre site. Fournit des outils de gestion pour modifier et supprimer des rôles.",
+ "PERMISSIONS_UPDATED" => "Autorisations mises à jour pour le rôle <strong>{{name}}</strong>",
+ "UPDATED" => "Détails mis à jour pour le rôle <strong>{{name}}</strong>"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "Informations sur le système",
+
+ "DB_NAME" => "Base de donnée",
+ "DB_VERSION" => "Version DB",
+ "DIRECTORY" => "Répertoire du projet",
+ "PHP_VERSION" => "Version de PHP",
+ "SERVER" => "Logiciel server",
+ "SPRINKLES" => "Sprinkles chargés",
+ "UF_VERSION" => "Version de UserFrosting",
+ "URL" => "Url racine"
+ ],
+
+ "USER" => [
+ 1 => "Utilisateur",
+ 2 => "Utilisateurs",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Changer le mot de passe",
+ "SEND_PASSWORD_LINK" => "Envoyer à l'utilisateur un lien qui lui permettra de choisir son propre mot de passe",
+ "SET_PASSWORD" => "Définissez le mot de passe de l'utilisateur comme"
+ ],
+
+ "ACTIVATE" => "Autoriser l'utilisateur",
+ "CREATE" => "Créer un utilisateur",
+ "CREATED" => "L'utilisateur <strong>{{user_name}}</strong> a été créé avec succès",
+ "DELETE" => "Supprimer l'utilisateur",
+ "DELETE_CONFIRM" => "Êtes-vous certain de vouloir supprimer l'utilisateur <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Oui, supprimer l'utilisateur",
+ "DISABLE" => "Désactiver l'utilisateur",
+ "EDIT" => "Modifier l'utilisateur",
+ "ENABLE" => "Activer l'utilisateur",
+ "INFO_PAGE" => "Page d'information de l'utilisateur pour {{name}}",
+ "PAGE_DESCRIPTION" => "Une liste des utilisateurs de votre site. Fournit des outils de gestion incluant la possibilité de modifier les détails de l'utilisateur, d'activer manuellement les utilisateurs, d'activer / désactiver les utilisateurs et plus.",
+ "LATEST" => "Derniers utilisateurs",
+ "VIEW_ALL" => "Voir tous les utilisateurs"
+ ],
+ "X_USER" => [
+ 0 => "Aucun utilisateur",
+ 1 => "{{plural}} utilisateur",
+ 2 => "{{plural}} utilisateurs"
+ ]
+]; \ No newline at end of file
diff --git a/main/app/sprinkles/admin/locale/it_IT/messages.php b/main/app/sprinkles/admin/locale/it_IT/messages.php
new file mode 100755
index 0000000..c40d5b3
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/it_IT/messages.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Italian message token translations for the 'admin' sprinkle.
+ * This translation was generated with Google translate. Please contribute if you are a native speaker.
+ *
+ * @package userfrosting\i18n\it
+ * @author Alexander Weissman
+ * @author Pietro Marangon (@Pe46dro)
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Attività",
+ 2 => "Attività",
+
+ "LAST" => "Ultima attività",
+ "PAGE" => "Un elenco delle attività degli utenti",
+ "TIME" => "Tempo di attività"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Cancellare la cache",
+ "CLEAR_CONFIRM" => "Sei sicuro di voler cancellare la cache del sito?",
+ "CLEAR_CONFIRM_YES" => "Sì, cancellare la cache",
+ "CLEARED" => "La cache è stata eliminata correttamente!"
+ ],
+
+ "DASHBOARD" => "Pannello di Controllo",
+ "NO_FEATURES_YET" => "Non sembra che alcune funzioni siano state create per questo account ... ancora. Forse non sono ancora state implementate, o forse qualcuno ha dimenticato di dare accesso. In entrambi i casi, siamo contenti di averti qui!",
+ "DELETE_MASTER" => "Non puoi eliminare l'account principale!",
+ "DELETION_SUCCESSFUL" => "Hai eliminato utente <strong>{{user_name}}</strong>.",
+ "DETAILS_UPDATED" => "Dettagli degli account aggiornati per l'utente <strong>{{user_name}}</strong>",
+ "DISABLE_MASTER" => "Non puoi disattivare l'account principale!",
+ "DISABLE_SELF" => "Non puoi disattivare il tuo account!",
+ "DISABLE_SUCCESSFUL" => "Account per l'utente <strong>{{user_name}}</strong> disattivato con successo!",
+ "ENABLE_SUCCESSFUL" => "Account per l'utente <strong>{{user_name}}</strong> attivato con successo.",
+
+ "GROUP" => [
+ 1 => "Gruppo",
+ 2 => "Gruppi",
+
+ "CREATE" => "Creare un gruppo",
+ "CREATION_SUCCESSFUL" => "Ha creato con successo il gruppo <strong>{{name}}</strong>",
+ "DELETE" => "Elimina gruppo",
+ "DELETE_CONFIRM" => "Sei sicuro di voler eliminare il gruppo <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Non puoi eliminare il gruppo <strong>{{name}}</strong> perché è il gruppo predefinito per gli utenti appena registrati.",
+ "DELETE_YES" => "Sì, elimini il gruppo",
+ "DELETION_SUCCESSFUL" => "Eliminato il gruppo <strong>{{name}}</strong> con successo",
+ "EDIT" => "Modifica gruppo",
+ "ICON" => "Icona del gruppo",
+ "ICON_EXPLAIN" => "Icona per i membri del gruppo",
+ "INFO_PAGE" => "Pagina informazioni di gruppo per <strong>{{name}}</strong>",
+ "MANAGE" => "Gestisci gruppo",
+ "NAME" => "Nome del gruppo",
+ "NAME_EXPLAIN" => "Inserisci un nome per il gruppo",
+ "NOT_EMPTY" => "Non puoi farlo perché ci sono ancora utenti associati al gruppo <strong>{{name}}</strong>.",
+ "PAGE_DESCRIPTION" => "Un elenco dei gruppi per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione di gruppi.",
+ "SUMMARY" => "Riepilogo del gruppo",
+ "UPDATE" => "Dettagli aggiornati per il gruppo <strong>{{name}}</strong>."
+ ],
+
+ "MANUALLY_ACTIVATED" => "<strong>{{user_name}}</strong> è stato attivato manualmente",
+ "MASTER_ACCOUNT_EXISTS" => "L'account primario esiste già!",
+ "MIGRATION" => [
+ "REQUIRED" => "È necessario aggiornare il database"
+ ],
+
+ "PERMISSION" => [
+ 1 => "Autorizzazione",
+ 2 => "Autorizzazioni",
+
+ "ASSIGN_NEW" => "Assegna nuova autorizzazione",
+ "HOOK_CONDITION" => "Hook/Condizioni",
+ "ID" => "ID di autorizzazione",
+ "INFO_PAGE" => "Pagina di informazioni sulle autorizzazioni per <strong>{{name}}</strong>",
+ "MANAGE" => "Gestione delle autorizzazioni",
+ "NOTE_READ_ONLY" => "<strong>Si prega di notare: le autorizzazioni</strong> sono considerate \"parte del codice\" e non possono essere modificate tramite l'interfaccia. Per aggiungere, rimuovere o modificare le autorizzazioni, i gestori del sito devono utilizzare <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about: _blank \">migrazione del database.</a>",
+ "PAGE_DESCRIPTION" => "Un elenco delle autorizzazioni per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione delle autorizzazioni.",
+ "SUMMARY" => "Sommario delle Autorizzazioni",
+ "UPDATE" => "Aggiorna le autorizzazioni",
+ "VIA_ROLES" => "Ha autorizzazione tramite ruoli"
+ ],
+
+ "ROLE" => [
+ 1 => "Ruolo",
+ 2 => "Ruoli",
+
+ "ASSIGN_NEW" => "Assegna un nuovo ruolo",
+ "CREATE" => "Crea ruolo",
+ "CREATION_SUCCESSFUL" => "Creato con successo il ruolo <strong>{{name}}</strong>",
+ "DELETE" => "Elimina il ruolo",
+ "DELETE_CONFIRM" => "Sei sicuro di voler eliminare il ruolo <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Non puoi eliminare il ruolo <strong>{{name}}</strong> perché è un ruolo predefinito per gli utenti appena registrati.",
+ "DELETE_YES" => "Sì, elimini il ruolo",
+ "DELETION_SUCCESSFUL" => "Eliminato il ruolo <strong>{{name}}</strong>",
+ "EDIT" => "Modifica ruolo",
+ "HAS_USERS" => "Non puoi farlo perché ci sono ancora utenti che hanno il ruolo <strong>{{name}}</strong>.",
+ "INFO_PAGE" => "Pagina di informazioni sui ruoli per <strong>{{name}}</strong>",
+ "MANAGE" => "Gestisci Ruoli",
+ "NAME" => "Nome",
+ "NAME_EXPLAIN" => "Inserisci un nome per il ruolo",
+ "NAME_IN_USE" => "Esiste già un ruolo denominato <strong>{{name}}</strong>",
+ "PAGE_DESCRIPTION" => "Un elenco dei ruoli per il tuo sito. Fornisce strumenti di gestione per la modifica e l'eliminazione di ruoli.",
+ "PERMISSIONS_UPDATED" => "Autorizzazioni aggiornate per ruolo <strong>{{name}}</strong>",
+ "SUMMARY" => "Riepilogo dei Ruoli",
+ "UPDATED" => "Dettagli aggiornati per ruolo <strong>{{name}}</strong>"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "Informazioni sul sistema",
+
+ "DB_NAME" => "Nome del database",
+ "DB_VERSION" => "Versione del database",
+ "DIRECTORY" => "Directory del progetto",
+ "PHP_VERSION" => "Versione PHP",
+ "SERVER" => "Software del webserver",
+ "SPRINKLES" => "Sprinkles caricati",
+ "UF_VERSION" => "Versione UserFrosting",
+ "URL" => "Url della radice del sito"
+ ],
+
+ "TOGGLE_COLUMNS" => "Scambia le colonne",
+
+ "USER" => [
+ 1 => "Utente",
+ 2 => "Utenti",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Cambia Password Utente",
+ "SEND_PASSWORD_LINK" => "Inviare all'utente un collegamento che permetterà loro di scegliere la propria password",
+ "SET_PASSWORD" => "Impostare la password dell'utente come"
+ ],
+
+ "ACTIVATE" => "Attiva l'utente",
+ "CREATE" => "Creare un utente",
+ "CREATED" => "Account per l'utente <strong>{{user_name}}</strong> è stato creato.",
+ "DELETE" => "Elimina utente",
+ "DELETE_CONFIRM" => "Sei sicuro di voler eliminare l'utente <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Sì, elimina l'utente",
+ "DISABLE" => "Disabilita l'utente",
+ "EDIT" => "Modifica utente",
+ "ENABLE" => "Abilita l'utente",
+ "INFO_PAGE" => "Pagina informazioni utente per <strong>{{name}}</strong>",
+ "LATEST" => "Ultimi Utenti",
+ "PAGE_DESCRIPTION" => "Un elenco degli utenti del tuo sito. Fornisce strumenti di gestione, tra cui la possibilità di modificare i dettagli utente, attivare manualmente gli utenti, abilitare / disabilitare gli utenti e altro ancora.",
+ "SUMMARY" => "Riepilogo account",
+ "VIEW_ALL" => "Visualizza tutti gli utenti",
+ "WITH_PERMISSION" => "Utenti con questa autorizzazione"
+ ],
+ "X_USER" => [
+ 0 => "Nessun utente",
+ 1 => "{{plural}} utente",
+ 2 => "{{plural}} utenti"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/pt_PT/messages.php b/main/app/sprinkles/admin/locale/pt_PT/messages.php
new file mode 100755
index 0000000..0faf818
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/pt_PT/messages.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Portuguese message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\pt
+ * @author Bruno Silva (brunomnsilva@gmail.com)
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Atividade",
+ 2 => "Atividades",
+
+ "LAST" => "Última atividade",
+ "PAGE" => "Lista de atividade dos utilizadores",
+ "TIME" => "Tempo da Atividade"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Limpar cache",
+ "CLEAR_CONFIRM" => "Tem a certeza que pretende limpar a cache do site?",
+ "CLEAR_CONFIRM_YES" => "Sim, limpar cache",
+ "CLEARED" => "Cache limpa com sucesso!"
+ ],
+
+ "DASHBOARD" => "Painel de Controlo",
+ "DELETE_MASTER" => "Não pode apagar a conta principal!",
+ "DELETION_SUCCESSFUL" => "Utilizador <strong>{{user_name}}</strong> foi removido com sucesso.",
+ "DETAILS_UPDATED" => "Detalhes de conta atualizados para o utilizador <strong>{{user_name}}</strong>",
+ "DISABLE_MASTER" => "Não pode desativar a conta principal!",
+ "DISABLE_SUCCESSFUL" => "Conta do utilizador <strong>{{user_name}}</strong> foi desativada com sucesso.",
+
+ "ENABLE_SUCCESSFUL" => "Conta do utilizador <strong>{{user_name}}</strong> foi ativada com sucesso.",
+
+ "GROUP" => [
+ 1 => "Grupo",
+ 2 => "Grupos",
+
+ "CREATE" => "Criar grupo",
+ "CREATION_SUCCESSFUL" => "Grupo criado com sucesso",
+ "DELETE" => "Remover grupo",
+ "DELETION_SUCCESSFUL" => "Grupo removido com sucesso",
+ "DELETE_CONFIRM" => "Tem a certeza que pretende remover o grupo <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Sim, remover grupo",
+ "EDIT" => "Editar grupo",
+ "ICON" => "Icon do grupo",
+ "ICON_EXPLAIN" => "Icon para membros do grupo",
+ "INFO_PAGE" => "Página informativa do grupo {{name}}",
+ //"MANAGE" => "Manage group",
+ "NAME" => "Nome do grupo",
+ "NAME_EXPLAIN" => "Por favor introduza um nome para o grupo",
+ "PAGE_DESCRIPTION" => "Lista de grupos do site. Contém opções para editar e remover grupos."
+ ],
+
+ "MANUALLY_ACTIVATED" => "A conta de {{user_name}} foi ativada manualmente.",
+ "MASTER_ACCOUNT_EXISTS" => "A contra principal já existe!",
+ "MIGRATION" => [
+ "REQUIRED" => "É necessário uma atualização da base de dados."
+ ],
+
+ "PERMISSION" => [
+ 1 => "Permissão",
+ 2 => "Permissões",
+
+ "ASSIGN_NEW" => "Atribuir nova permissão",
+ "HOOK_CONDITION" => "Hook/Condições",
+ "MANAGE" => "Gerir permissões",
+ "PAGE_DESCRIPTION" => "Lista de permissões do site. Contém opções para editar e remover permissões.",
+ "UPDATE" => "Atualizar permissões"
+ ],
+
+ "ROLE" => [
+ 1 => "Cargo",
+ 2 => "Cargos",
+
+ "ASSIGN_NEW" => "Atribuir novo cargo",
+ "CREATE" => "Criar cargo",
+ "CREATION_SUCCESSFUL" => "Cargo criado com sucesso",
+ "DELETE" => "Remover cargo",
+ "DELETION_SUCCESSFUL" => "Cargo removido com sucesso",
+ "DELETE_CONFIRM" => "Tem a certeza que pretende remover o cargo <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Sim, remover cargo",
+ "EDIT" => "Editar cargo",
+ "INFO_PAGE" => "Página informativa do cargo {{name}}",
+ "MANAGE" => "Gerir cargos",
+ "NAME" => "Nome",
+ "NAME_EXPLAIN" => "Por favor introduza um nome para o cargo",
+ "PAGE_DESCRIPTION" => "Lista de cargos do site. Contém opções para editar e remover cargos.",
+ "UPDATE" => "Atualizar cargos",
+ "UPDATED" => "Cargo <strong>{{name}}</strong> atualizado"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "Informação do sistema",
+
+ "DB_NAME" => "Nome da base de dados",
+ "DB_VERSION" => "Versão da base de dados",
+ "DIRECTORY" => "Diretório do projeto",
+ "PHP_VERSION" => "Versão PHP",
+ "SERVER" => "Software do servidor web",
+ "SPRINKLES" => "Sprinkles carregados",
+ "UF_VERSION" => "Versão do UserFrosting",
+ "URL" => "Raiz (url) do site"
+ ],
+
+ "USER" => [
+ 1 => "Utilizador",
+ 2 => "Utilizadores",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Alterar password",
+ "SEND_PASSWORD_LINK" => "Enviar um link ao utilizador que lhe permita escolher a sua password",
+ "SET_PASSWORD" => "Definir a password do utilizador como"
+ ],
+
+ "ACTIVATE" => "Ativar utilizador",
+ "CREATE" => "Criar utilizador",
+ "DELETE" => "Remover utilizador",
+ "DELETE_CONFIRM" => "Tem a certeza que pretende remover o utilizador <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Sim, remover utilizador",
+ "DISABLE" => "Desativar utilizador",
+ "EDIT" => "Editar utilizador",
+ "ENABLE" => "Ativar utilizador",
+ "INFO_PAGE" => "Página informativa do utilizador {{name}}",
+ "PAGE_DESCRIPTION" => "Lista de utilizadores do site. Contém opções para editar detalhes, ativar/desativar utilizadores e outras.",
+ "LATEST" => "Últimos Utilizadores",
+ "VIEW_ALL" => "Ver todos os utilizadores"
+ ],
+ "X_USER" => [
+ 0 => "Nenhum utilizador",
+ 1 => "{{plural}} utilizador",
+ 2 => "{{plural}} utilizadores"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/ru_RU/messages.php b/main/app/sprinkles/admin/locale/ru_RU/messages.php
new file mode 100755
index 0000000..d6f6e1a
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/ru_RU/messages.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Russian message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\ru_RU
+ * @author @rendername
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Активность",
+ 2 => "Активность",
+
+ "LAST" => "Последняя активность",
+ "PAGE" => "Список действий пользователя",
+ "TIME" => "Время действия"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Очистить кэш",
+ "CLEAR_CONFIRM" => "Уверены, что хотите очистить кэш сайта?",
+ "CLEAR_CONFIRM_YES" => "Да, очистить кэш",
+ "CLEARED" => "Кэш успешно очищен !"
+ ],
+
+ "DASHBOARD" => "Панель управления",
+ "NO_FEATURES_YET" => "Похоже, некоторые функции не были настроены для этого аккаунта... пока. Возможно, они еще не реализованы, или, может быть, кто-то забыл дать вам доступ. В любом случае, мы рады вас видеть здесь!",
+ "DELETE_MASTER" => "Нельзя удалить главный аккаунт!",
+ "DELETION_SUCCESSFUL" => "Пользователь <strong>{{user_name}}</strong> был успешно удален.",
+ "DETAILS_UPDATED" => "Данные для аккаунта <strong>{{user_name}}</strong> обновлены",
+ "DISABLE_MASTER" => "Нельзя отключить главный аккаунт!",
+ "DISABLE_SELF" => "Вы не можете отключить собственный аккаунт!",
+ "DISABLE_SUCCESSFUL" => "Пользователь <strong>{{user_name}}</strong> был успешно отключен.",
+
+ "ENABLE_SUCCESSFUL" => "Пользователь <strong>{{user_name}}</strong> был успешно включен.",
+
+ "GROUP" => [
+ 1 => "Группа",
+ 2 => "Группы",
+
+ "CREATE" => "Создать группу",
+ "CREATION_SUCCESSFUL" => "Успешно создана группа <strong>{{name}}</strong>",
+ "DELETE" => "Удалить группу",
+ "DELETE_CONFIRM" => "Вы уверены, что вы хотите удалить группу <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Нельзя удалить группу <strong>{{name}}</strong>, потому что это группа по умолчанию для новых пользователей.",
+ "DELETE_YES" => "Да, удалить группу",
+ "DELETION_SUCCESSFUL" => "Успешно удалена группа <strong>{{name}}</strong>",
+ "EDIT" => "Редактор группы",
+ "ICON" => "Значок группы",
+ "ICON_EXPLAIN" => "Значок для членов группы",
+ "INFO_PAGE" => "Описание группы {{name}}",
+ "MANAGE" => "Управление группой",
+ "NAME" => "Имя группы",
+ "NAME_EXPLAIN" => "Пожалуйста, введите имя группы",
+ "NOT_EMPTY" => "Вы не можете сделать это, потому что до сих пор есть пользователи, связанные с группой <strong>{{name}}</strong>.",
+ "PAGE_DESCRIPTION" => "Список групп для вашего сайта. Предоставляет инструменты управления для редактирования и удаления групп.",
+ "SUMMARY" => "Резюме группы",
+ "UPDATE" => "Информация обновлена для группы <strong>{{name}}</strong>"
+ ],
+
+ "MANUALLY_ACTIVATED" => "{{user_name}} аккаунт был активирован вручную",
+ "MASTER_ACCOUNT_EXISTS" => "Мастер-аккаунт уже существует!",
+ "MIGRATION" => [
+ "REQUIRED" => "Необходимо обновление базы данных"
+ ],
+
+ "PERMISSION" => [
+ 1 => "Доступ",
+ 2 => "Права доступа",
+
+ "ASSIGN_NEW" => "Назначить новые права",
+ "HOOK_CONDITION" => "Привязки/Условия",
+ "ID" => "ID доступа",
+ "INFO_PAGE" => "Информация о доступах для '{{name}}'",
+ "MANAGE" => "Управление правами",
+ "NOTE_READ_ONLY" => "<strong>Пожалуйста, обратите внимание:</strong> <u>права и доступы</u> считаются \"частью кода\" и не могут быть изменены через интерфейс. Чтобы добавить, удалить или изменить доступы, нужно будет использовать сайт <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about:_blank\"> базы данных миграции.</a>",
+ "PAGE_DESCRIPTION" => "Список прав доступа для вашего сайта. Предоставляет инструменты управления для редактирования и удаления прав.",
+ "SUMMARY" => "Сводка доступов",
+ "UPDATE" => "Обновление прав",
+ "VIA_ROLES" => "Имеет права через роли"
+ ],
+
+ "ROLE" => [
+ 1 => "Роль",
+ 2 => "Роли",
+
+ "ASSIGN_NEW" => "Назначение новой роли",
+ "CREATE" => "Создать роль",
+ "CREATION_SUCCESSFUL" => "Успешно создана роль <strong>{{name}}</strong>",
+ "DELETE" => "Удалить роль",
+ "DELETE_CONFIRM" => "Вы уверены, что вы хотите удалить роль <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Невозможно удалить роль <strong>{{name}}</strong>, потому что это роль по умолчанию для новых пользователей.",
+ "DELETE_YES" => "Да, удалить роль",
+ "DELETION_SUCCESSFUL" => "Успешно удалена роль <strong>{{name}}</strong>",
+ "EDIT" => "Изменить роль",
+ "HAS_USERS" => "Вы не можете сделать это, потому что до сих пор есть пользователи, имеющие роль <strong>{{name}}</strong>.",
+ "INFO_PAGE" => "Информации роли для {{name}}",
+ "MANAGE" => "Управление ролями",
+ "NAME" => "Имя",
+ "NAME_EXPLAIN" => "Пожалуйста, укажите имя для роли",
+ "NAME_IN_USE" => "Роль с именем <strong>{{name}}</strong> уже существует",
+ "PAGE_DESCRIPTION" => "Список ролей для вашего сайта. Предоставляет инструменты управления для редактирования и удаления ролей.",
+ "PERMISSIONS_UPDATED" => "Права обновлены для роли <strong>{{name}}</strong>",
+ "SUMMARY" => "Основная информация",
+ "UPDATED" => "Информация для роли <strong>{{name}}</strong>"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "Системная информация",
+
+ "DB_NAME" => "Имя базы данных",
+ "DB_VERSION" => "Версия базы данных",
+ "DIRECTORY" => "Каталог проекта",
+ "PHP_VERSION" => "Версия PHP",
+ "SERVER" => "ПО Сервера",
+ "SPRINKLES" => "Загружены модули",
+ "UF_VERSION" => "Версия UserFrosting",
+ "URL" => "URL-адрес сайта"
+ ],
+
+ "TOGGLE_COLUMNS" => "Переключатель столбцов",
+
+ "USER" => [
+ 1 => "Пользователь",
+ 2 => "Пользователи",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Сменить пароль пользователя",
+ "SEND_PASSWORD_LINK" => "Отправить пользователю ссылку, которая позволит выбрать свой собственный пароль",
+ "SET_PASSWORD" => "Установите пароль пользователя как"
+ ],
+
+ "ACTIVATE" => "Активировать пользователя",
+ "CREATE" => "Создать пользователя",
+ "CREATED" => "Пользователь <strong>{{user_name}}</strong> был успешно создан",
+ "DELETE" => "Удалить пользователя",
+ "DELETE_CONFIRM" => "Вы уверены, что вы хотите удалить пользователя <strong>{{name}}</strong>?",
+ "DELETE_YES" => "Да, удалить пользователя",
+ "DELETED" => "Пользователь удален",
+ "DISABLE" => "Отключить пользователя",
+ "EDIT" => "Изменить пользователя",
+ "ENABLE" => "Включить пользователя",
+ "INFO_PAGE" => "Информация о пользователе для {{name}}",
+ "LATEST" => "Последние пользователи",
+ "PAGE_DESCRIPTION" => "Список пользователей для вашего сайта. Предоставляет средства управления, включая возможность редактирования сведений о пользователях, ручной активации пользователей, включения/отключения пользователей и многое другое.",
+ "SUMMARY" => "Сводка аккаунта",
+ "VIEW_ALL" => "Все пользователи",
+ "WITH_PERMISSION" => "Пользователи с этим доступом"
+ ],
+ "X_USER" => [
+ 0 => "Нет пользователей",
+ 1 => "{{plural}} пользователя",
+ 2 => "{{plural}} пользователей"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/th_TH/messages.php b/main/app/sprinkles/admin/locale/th_TH/messages.php
new file mode 100755
index 0000000..546232d
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/th_TH/messages.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Thai message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\th
+ * @author Karuhut Komol
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "กิจกรรม",
+ 2 => "กิจกรรม",
+
+ "LAST" => "กิจกรรมล่าสุด",
+ "PAGE" => "รายการกิจกรรมของผู้ใช้",
+ "TIME" => "เวลาที่ทำกิจกรรม"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "ล้างแคช",
+ "CLEAR_CONFIRM" => "คุณแน่ใจหรือที่จะล้างแคชของเว็บ?",
+ "CLEAR_CONFIRM_YES" => "ใช่ ล้างแคชเลย",
+ "CLEARED" => "ล้างแคชเรียบร้อยแล้ว!"
+ ],
+
+ "DASHBOARD" => "แผงควบคุม",
+ "DELETE_MASTER" => "คุณไม่สามารถลบบัญชีหลักได้!",
+ "DELETION_SUCCESSFUL" => "ลบผู้ใช้ <strong>{{user_name}}</strong> เรียบร้อยแล้ว",
+ "DETAILS_UPDATED" => "ปรับปรุงรายระเอียดบัญชีให้กับ <strong>{{user_name}}</strong> แล้ว",
+ "DISABLE_MASTER" => "คุณไม่สามารถปิดการใช้งานบัญชีหลัก!",
+ "DISABLE_SUCCESSFUL" => "ปิดการใช้งานบัญชีของผู้ใช้ <strong>{{user_name}}</strong> เรียบร้อยแล้ว",
+
+ "ENABLE_SUCCESSFUL" => "เปิดการใช้งานบัญชีของผู้ใช้ <strong>{{user_name}}</strong> เรียบร้อยแล้ว",
+
+ "GROUP" => [
+ 1 => "กลุ่ม",
+ 2 => "กลุ่ม",
+
+ "CREATE" => "สร้างกลุ่ม",
+ "DELETE" => "ลบกลุ่ม",
+ "DELETE_CONFIRM" => "คุณแน่ใจหรือที่จะลบกลุ่ม <strong>{{name}}</strong>?",
+ "DELETE_YES" => "ใช่ ลบกลุ่มนี้เลย",
+ "EDIT" => "แก้ไขกลุ่ม",
+ "ICON" => "ไอคอนกลุ่ม",
+ "ICON_EXPLAIN" => "ไอคอนสำหรับสมาชิกกลุ่ม",
+ "INFO_PAGE" => "หน้าข้อมูลกลุ่มสำหรับ {{name}}",
+ //"MANAGE" => "Manage group",
+ "NAME" => "ชื่อกลุ่ม",
+ "NAME_EXPLAIN" => "กรุณาตั้งชื่อสำหรับกลุ่มนี้",
+ "PAGE_DESCRIPTION" => "รายชื่อกลุ่มในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับการแก้ไขและลบกลุ่ม"
+ ],
+
+ "MANUALLY_ACTIVATED" => "บัญชีของ {{user_name}} ได้เปิดใช้งานเองแล้ว",
+ "MASTER_ACCOUNT_EXISTS" => "มีบัญชีหลักอยู่แล้ว!",
+ "MIGRATION" => [
+ "REQUIRED" => "ต้องการการปรับปรุงฐานข้อมูล"
+ ],
+
+ "PERMISSION" => [
+ 1 => "สิทธิการเข้าถึง",
+ 2 => "สิทธิการเข้าถึง",
+
+ "ASSIGN_NEW" => "กำหนดสิทธิการเข้าถึงใหม่",
+ "HOOK_CONDITION" => "ข้อกำหนด/เงื่อนไข",
+ "MANAGE" => "จัดการสิทธิการเข้าถึง",
+ "PAGE_DESCRIPTION" => "รายการสิทธิการเข้าถึงในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับการแก้ไขและลบสิทธิการเข้าถึง",
+ "UPDATE" => "ปรับปรุงสิทธิการเข้าถึง"
+ ],
+
+ "ROLE" => [
+ 1 => "ตำแหน่ง",
+ 2 => "ตำแหน่ง",
+
+ "ASSIGN_NEW" => "กำหนดตำแหน่งใหม่",
+ "CREATE" => "สร้างตำแหน่ง",
+ "DELETE" => "ลบตำแหน่ง",
+ "DELETE_CONFIRM" => "คุณแน่ใจหรือที่จะลบตำแหน่ง <strong>{{name}}</strong>?",
+ "DELETE_YES" => "ใช่ ลบตำแหน่งนี้เลย",
+ "EDIT" => "แก้ไขตำแหน่ง",
+ "INFO_PAGE" => "หน้าข้อมูลตำแหน่งสำหรับ {{name}}",
+ "MANAGE" => "จัดการตำแหน่ง",
+ "NAME" => "ชื่อ",
+ "NAME_EXPLAIN" => "กรุณาตั้งชื่อสำหรับตำแหน่งนี้",
+ "PAGE_DESCRIPTION" => "รายชื่อตำแหน่งในเว็บของคุณ ประกอบไปด้วยเครื่องมือในการจัดการสำหรับแก้ไขและลบตำแหน่ง",
+ "UPDATED" => "ปรับปรุงตำแหน่ง"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "ข้อมูลระบบ",
+
+ "DB_NAME" => "ชื่อฐานข้อมูล",
+ "DB_VERSION" => "เวอร์ชั่นฐานข้อมูล",
+ "DIRECTORY" => "ไดเรกทอรีของโปรเจค",
+ "PHP_VERSION" => "เวอร์ชั่น PHP",
+ "SERVER" => "ซอฟต์แวร์เว็บเซิร์ฟเวอร์",
+ "SPRINKLES" => "Sprinkles ที่ถูกโหลด",
+ "UF_VERSION" => "เวอร์ชั่น UserFrosting",
+ "URL" => "URL ของรากเว็บไซต์"
+ ],
+
+ "USER" => [
+ 1 => "ผู้ใช้",
+ 2 => "ผู้ใช้",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "เปลี่ยนรหัสผ่านผู้ใช้",
+ "SEND_PASSWORD_LINK" => "ส่งลิงก์ที่จะอนุญาตให้ผู้ใช้เลือกรหัสผ่านเองให้กับผู้ใช้",
+ "SET_PASSWORD" => "ตั้งรหัสผ่านของผู้ใช้เป็น"
+ ],
+
+ "ACTIVATE" => "เปิดใช้งานผู้ใช้",
+ "CREATE" => "สร้างผู้ใช้",
+ "DELETE" => "ลบผู้ใช้",
+ "DELETE_CONFIRM" => "คุณแน่ใจหรือที่จะลบผู้ใช้ <strong>{{name}}</strong>?",
+ "DELETE_YES" => "ใช่ ลบผู้ใช้นี้เลย",
+ "DISABLE" => "ปิดการใช้งานผู้ใช้",
+ "EDIT" => "แก้ไขผู้ใช้",
+ "ENABLE" => "เปิดการใช้งานผู้ใช้",
+ "INFO_PAGE" => "หน้าข้อมูลของผู้ใช้ {{name}}",
+ "PAGE_DESCRIPTION" => "รายชื่อผู้ใช้ในเว็บของคุณ ประกอบไปด้วยเครื่องมือสำหรับการจัดการ รวมทั้งความสามารถในการแก้ไขรายละเอียดผู้ใช้ การเปิดใช้งานผู้ใช้ การเปิด/ปิดบัญชีผู้ใช้และอื่น ๆ",
+ "LATEST" => "ผู้ใช้ล่าสุด",
+ "VIEW_ALL" => "ดูผู้ใช้ทั้งหมด"
+ ],
+ "X_USER" => [
+ 0 => "ไม่มีผู้ใช้",
+ 1 => "{{plural}} ผู้ใช้",
+ 2 => "{{plural}} ผู้ใช้"
+ ]
+]; \ No newline at end of file
diff --git a/main/app/sprinkles/admin/locale/tr/messages.php b/main/app/sprinkles/admin/locale/tr/messages.php
new file mode 100755
index 0000000..25d5633
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/tr/messages.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Turkish message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\tr
+ * @author Dumblledore
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "Etkinlik",
+ 2 => "Etkinlikler",
+
+ "LAST" => "Son Etkinlik",
+ "PAGE" => "Kullanıcı etkinliklerinin listesi",
+ "TIME" => "Aktivite zamanı"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "Önbelleği temizle",
+ "CLEAR_CONFIRM" => "Site önbelleğini temizlemek istediğine emin misin?",
+ "CLEAR_CONFIRM_YES" => "Evet, önbelleği temizle",
+ "CLEARED" => "Önbellek temizlenmesi başarıyla tamamlandı!"
+ ],
+
+ "DASHBOARD" => "Pano",
+ "NO_FEATURES_YET" => "Bu hesap için herhangi bir özellik ayarlanmış gibi görünmüyor... Henüz. Belki de henüz uygulanmadı, veya belki birisi size erişim vermeyi unutttu. Her iki durumda da sizi aramızda gördüğümüze sevindik!",
+ "DELETE_MASTER" => "Ana hesabı silemezsiniz!",
+ "DELETION_SUCCESSFUL" => "Kullanıcı<strong>{{user_name}}</strong> silme işlemi başarıyla tamamlandı.",
+ "DETAILS_UPDATED" => "Kullanıcı<strong>{{user_name}}</strong> için güncel hesap detayları",
+ "DISABLE_MASTER" => "Ana hesabı devre dışı bırakamazsınız!",
+ "DISABLE_SELF" => "Kendi hesabınızın etkinliğini sonlandıramazsınız!",
+ "DISABLE_SUCCESSFUL" => "Kullanıcı hesabın <strong>{{user_name}}</strong>başarıyla devre dışı bırakıldı.",
+
+ "ENABLE_SUCCESSFUL" => "Kullanıcı hesabın<strong>{{user_name}}</strong>başarıyla etkinleştirildi.",
+
+ "GROUP" => [
+ 1 => "Grup",
+ 2 => "Grıplar",
+
+ "CREATE" => "Grup oluşturmak",
+ "CREATION_SUCCESSFUL" => "Grup oluşturma başarılı<strong>{{name}}</strong>",
+ "DELETE" => "Grubu sil",
+ "DELETE_CONFIRM" => "Grubu silmek istediğine emin misin<strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Grubu silemezsin<strong>{{name}}</strong> çünkü o yeni kayıtlanan kullanıcılar için varsayılan grup.",
+ "DELETE_YES" => "Evet, grubu sil",
+ "DELETION_SUCCESSFUL" => "Grup silme başarılı<strong>{{name}}</strong>",
+ "EDIT" => "Grubu düzenle",
+ "ICON" => "Grup ikonu",
+ "ICON_EXPLAIN" => "Grup iyileri için ikon",
+ "INFO_PAGE" => "Grup bilgisi sayfası {{name}} için",
+ "MANAGE" => "Grubu yönet",
+ "NAME" => "Grup adı",
+ "NAME_EXPLAIN" => "Lütfen grup için bir isim giriniz",
+ "NOT_EMPTY" => "Bunu yapamazsınız çünkü hala grupla ilişkili kullanıcılar var<strong>{{name}}</strong>.",
+ "PAGE_DESCRIPTION" => "Siten için grupların bir listesi. Grupları silmek ve düzenlemek için yönetim araçları sağlar.",
+ "SUMMARY" => "Grup özeti",
+ "UPDATE" => "Grup için detaylar güncellendi<strong>{{name}}</strong>"
+ ],
+
+ "MANUALLY_ACTIVATED" => "{{user_name}}'ın hesabı el ile aktifleştirildi",
+ "MASTER_ACCOUNT_EXISTS" => "Ana hesap zaten mevcut!",
+ "MIGRATION" => [
+ "REQUIRED" => "Veritabanını güncellemek gerek"
+ ],
+
+ "PERMISSION" => [
+ 1 => "İzin",
+ 2 => "İzinler",
+
+ "ASSIGN_NEW" => "Yeni izin ata",
+ "HOOK_CONDITION" => "Kanca/Koşullar",
+ "ID" => "İzin Kimliği",
+ "INFO_PAGE" => "{{name}} için izin bilgi sayfası",
+ "MANAGE" => "İzinleri yönet",
+ "NOTE_READ_ONLY" => "<strong>Lütfen Dikkat</strong> izinler ''bir kodun parçası'' olarak kabul edilir ve arayüz aracılığıyla değiştirilemez. İzln eklemek, kaldırmak ya da değiştirmek için site bakımcıları bir <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about:_blank\">veritabanı geçişi</a> kullanmalıdır",
+ "PAGE_DESCRIPTION" => "Siteniz için izinlerin bir listesi. Düzenleme yapmak ve izinleri kaldırmak yönetim araçları temin eder.",
+ "SUMMARY" => "İzin Özeti",
+ "UPDATE" => "İzinlerin Güncellenmesi",
+ "VIA_ROLES" => "Roller ile izin alımı"
+ ],
+
+ "ROLE" => [
+ 1 => "Rol",
+ 2 => "Roller",
+
+ "ASSIGN_NEW" => "Yeni rol ata",
+ "CREATE" => "Rol oluştur",
+ "CREATION_SUCCESSFUL" => "Rol oluşturma başarılı <strong>{{name}}</strong>",
+ "DELETE" => "Rolü sil",
+ "DELETE_CONFIRM" => "Rolü silmek istediğine emin misin <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "Rolü silemezsin <strong>{{name}}</strong> çünkü o kaydolmuş kullanıcılar için varsayılan bir rol.",
+ "DELETE_YES" => "Evet, rolü sil",
+ "DELETION_SUCCESSFUL" => "Rol başarıyla silindi<strong>{{name}}</strong>",
+ "EDIT" => "Rolü düzenle",
+ "HAS_USERS" => "Bunu yapamazsın çünkü hala bu rol ile bağlantılı kullanıcılar var<strong>{{name}}</strong>.",
+ "INFO_PAGE" => "{{name}} için rol bilgi sayfası",
+ "MANAGE" => "Rolleri yönet",
+ "NAME" => "Ad",
+ "NAME_EXPLAIN" => "Lütfen rol için bir ad giriniz",
+ "NAME_IN_USE" => "<strong>{{name}}</strong> adında bir rol zaten mevcut",
+ "PAGE_DESCRIPTION" => "Siteniz için rollerin bir listesi. Düzenlemek ve rolleri silmek için yönetim araçları sağlar.",
+ "PERMISSIONS_UPDATED" => "Rol için izinler güncellendi<strong>{{name}}</strong>",
+ "SUMMARY" => "Rol özeti",
+ "UPDATED" => "Rol için detaylar güncellendi<strong>{{name}}</strong>"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "Sistem bilgisi",
+
+ "DB_NAME" => "Veritabanı adı",
+ "DB_VERSION" => "Veritabanı sürümü",
+ "DIRECTORY" => "Proje dizini",
+ "PHP_VERSION" => "PHP sürümü",
+ "SERVER" => "Web sunucu yazılımı",
+ "SPRINKLES" => "Yüklü serpintiler",
+ "UF_VERSION" => "UserFrosting sürümü",
+ "URL" => "Site kök url"
+ ],
+
+ "TOGGLE_COLUMNS" => "Sütünları değiştirme",
+
+ "USER" => [
+ 1 => "Kullanıcı",
+ 2 => "Kullanıcılar",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "Kullanıcı şifresini değiştir",
+ "SEND_PASSWORD_LINK" => "Kullanıcıya kendi şifresini seçebileceği bir bağlantı gönder",
+ "SET_PASSWORD" => "Kullanıcının şifresi olarak ayarla"
+ ],
+
+ "ACTIVATE" => "Aktif Kullanıcı",
+ "CREATE" => "Kullanıcı oluştur",
+ "CREATED" => "Kullanıcı <strong>{{user_name}}</strong> başarıyla oluşturuldu",
+ "DELETE" => "Kullanıcıyı sil",
+ "DELETE_CONFIRM" => "Kullanıcıyı silmek istediğinden emin misin?<strong>{{name}}</strong>?",
+ "DELETE_YES" => "Evet, kullanıcıyı sil",
+ "DELETED" => "Kullanıcı silindi",
+ "DISABLE" => "Kullanıcı devre dışı",
+ "EDIT" => "Kullanıcıyı düzenle",
+ "ENABLE" => "Kullanıcı etkin",
+ "INFO_PAGE" => "{{name}} için kullanıcı bilgisi",
+ "LATEST" => "Son Kullanıcılar",
+ "PAGE_DESCRIPTION" => "Siten için kullanıcıların listesi. Kullanıcı detaylarını düzenlemek, elle kullanıcıları aktifleştirmek, kullanıcıları etkinleştirme/devre dışı bırakma, ve daha fazlası için yönetimsel araçlar sağlar.",
+ "SUMMARY" => "Hesap özeti",
+ "VIEW_ALL" => "Tüm kullanıcıları göster",
+ "WITH_PERMISSION" => "Bu izni olan kullanıcılar"
+ ],
+ "X_USER" => [
+ 0 => "Kullanıcı yok",
+ 1 => "{{plural}} kullanıcı",
+ 2 => "{{plural}} kullanıcılar"
+ ]
+];
diff --git a/main/app/sprinkles/admin/locale/zh_CN/messages.php b/main/app/sprinkles/admin/locale/zh_CN/messages.php
new file mode 100755
index 0000000..2adc8c8
--- /dev/null
+++ b/main/app/sprinkles/admin/locale/zh_CN/messages.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ *
+ * Chinese message token translations for the 'admin' sprinkle.
+ *
+ * @package userfrosting\i18n\zh_CN
+ * @author @BruceGui (https://github.com/BruceGui)
+ */
+
+return [
+ "ACTIVITY" => [
+ 1 => "活动",
+ 2 => "活动",
+
+ "LAST" => "最后活动",
+ "PAGE" => "用户活动列表",
+ "TIME" => "活动时间"
+ ],
+
+ "ADMIN" => [
+ "PANEL" => "控制台"
+ ],
+
+ "CACHE" => [
+ "CLEAR" => "清除缓存",
+ "CLEAR_CONFIRM" => "你确定要清除此网站的缓存?",
+ "CLEAR_CONFIRM_YES" => "是的, 清除缓存",
+ "CLEARED" => "缓存成功清除 !"
+ ],
+
+ "DASHBOARD" => "仪表盘",
+ "DELETE_MASTER" => "你不能删除超级账户!",
+ "DELETION_SUCCESSFUL" => "用户 <strong>{{user_name}}</strong> 删除成功.",
+ "DETAILS_UPDATED" => "用户 <strong>{{user_name}}</strong> 更新成功",
+ "DISABLE_MASTER" => "你不能禁用超级用户!",
+ "DISABLE_SELF" => "不能禁用你自己的账户!",
+ "DISABLE_SUCCESSFUL" => "用户名<strong>{{user_name}}</strong> 成功禁用.",
+
+ "ENABLE_SUCCESSFUL" => "用户名 <strong>{{user_name}}</strong> 启用成功.",
+
+ "GROUP" => [
+ 1 => "组",
+ 2 => "组",
+
+ "CREATE" => "新建组",
+ "CREATION_SUCCESSFUL" => "成功创建组 <strong>{{name}}</strong>",
+ "DELETE" => "删除组",
+ "DELETE_CONFIRM" => "确定要删除组 <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "你无法删除组 <strong>{{name}}</strong> 因为这是新注册用户的默认组.",
+ "DELETE_YES" => "是, 删除组",
+ "DELETION_SUCCESSFUL" => "成功删除组<strong>{{name}}</strong>",
+ "EDIT" => "编辑组",
+ "ICON" => "组图标",
+ "ICON_EXPLAIN" => "组成员图标",
+ "INFO_PAGE" => "{{name}} 组的信息页",
+ "NAME" => "组名",
+ "NAME_EXPLAIN" => "请为组取一个名字",
+ "NOT_EMPTY" => "你不能这样做,因为还有用户在组 <strong>{{name}}</strong>.",
+ "PAGE_DESCRIPTION" => "网站组列表. 请管理编辑和删除组的工具.",
+ "SUMMARY" => "组简介",
+ "UPDATE" => "组<strong>{{name}}</strong>的信息已经更新"
+ ],
+
+ "MANUALLY_ACTIVATED" => "{{user_name}} 账户已手动激活",
+ "MASTER_ACCOUNT_EXISTS" => "超级账户已存在!",
+ "MIGRATION" => [
+ "REQUIRED" => "数据库需要更新"
+ ],
+
+ "PERMISSION" => [
+ 1 => "权限",
+ 2 => "权限",
+
+ "ASSIGN_NEW" => "分配新权限",
+ "HOOK_CONDITION" => "Hook/条件",
+ "ID" => "权限ID",
+ "INFO_PAGE" => "用户 '{{name}}' 的权限页",
+ "MANAGE" => "管理权限",
+ "NOTE_READ_ONLY" => "<strong>请注意:</strong> 权限是 \"part of the code\" 不能通过界面修改. 如果要添加、删除、编辑权限, 网站维护者需要 <a href=\"https://learn.userfrosting.com/database/extending-the-database\" target=\"about:_blank\">进行数据库迁移.</a>",
+ "PAGE_DESCRIPTION" => "网站的权限列表. 提供编辑和删除权限的工具.",
+ "SUMMARY" => "权限概要",
+ "UPDATE" => "更新权限",
+ "VIA_ROLES" => "权限通过用户了"
+ ],
+
+ "ROLE" => [
+ 1 => "角色",
+ 2 => "角色",
+
+ "ASSIGN_NEW" => "分配角色",
+ "CREATE" => "创建角色",
+ "CREATION_SUCCESSFUL" => "成功创建角色 <strong>{{name}}</strong>",
+ "DELETE" => "删除角色",
+ "DELETE_CONFIRM" => "你确定要删除角色 <strong>{{name}}</strong>?",
+ "DELETE_DEFAULT" => "你无法删除角色<strong>{{name}}</strong> 因为这是新注册用户的默认角色.",
+ "DELETE_YES" => "是, 删除角色",
+ "DELETION_SUCCESSFUL" => "成功删除角色 <strong>{{name}}</strong>",
+ "EDIT" => "编辑角色",
+ "HAS_USERS" => "你不能这样做,因为还有用户拥有角色 <strong>{{name}}</strong>.",
+ "INFO_PAGE" => "{{name}}角色的权限页",
+ "MANAGE" => "管理角色",
+ "NAME" => "名字",
+ "NAME_EXPLAIN" => "请为角色输入名字",
+ "NAME_IN_USE" => "角色名 <strong>{{name}}</strong> 以存在",
+ "PAGE_DESCRIPTION" => "网站的角色列表. 请管理编辑和删除角色的工具.",
+ "PERMISSIONS_UPDATED" => "角色 <strong>{{name}}</strong> 的权限更新成功",
+ "SUMMARY" => "角色概要",
+ "UPDATED" => "角色 <strong>{{name}}</strong> 更新成功"
+ ],
+
+ "SYSTEM_INFO" => [
+ "@TRANSLATION" => "系统信息",
+
+ "DB_NAME" => "数据库",
+ "DB_VERSION" => "数据库版本",
+ "DIRECTORY" => "工程目录",
+ "PHP_VERSION" => "PHP 版本",
+ "SERVER" => "网站服务器",
+ "SPRINKLES" => "加载的 sprinkles",
+ "UF_VERSION" => "UserFrosting 版本",
+ "URL" => "网站根目录"
+ ],
+
+ "TOGGLE_COLUMNS" => "加载列",
+
+ "USER" => [
+ 1 => "用户",
+ 2 => "用户",
+
+ "ADMIN" => [
+ "CHANGE_PASSWORD" => "修改用户密码",
+ "SEND_PASSWORD_LINK" => "发送允许该用户修改密码的链接",
+ "SET_PASSWORD" => "设置用户的密码为"
+ ],
+
+ "ACTIVATE" => "激活用户",
+ "CREATE" => "新建用户",
+ "CREATED" => "用户 <strong>{{user_name}}</strong> 新建成功",
+ "DELETE" => "删除用户",
+ "DELETE_CONFIRM" => "确定要删除用户 <strong>{{name}}</strong>?",
+ "DELETE_YES" => "是, 删除用户",
+ "DISABLE" => "禁用用户",
+ "EDIT" => "编辑用户",
+ "ENABLE" => "启用用户",
+ "INFO_PAGE" => "用户 {{name}} 的信息页",
+ "LATEST" => "最新用户",
+ "PAGE_DESCRIPTION" => "网站用户列表. 提供编辑用户信息, 手动激活用户, 启用/禁用 用户等工具.",
+ "SUMMARY" => "账户概要",
+ "VIEW_ALL" => "浏览所有用户",
+ "WITH_PERMISSION" => "有此权限的用户"
+ ],
+ "X_USER" => [
+ 0 => "没有用户",
+ 1 => "{{plural}} 用户",
+ 2 => "{{plural}} 用户"
+ ]
+];
diff --git a/main/app/sprinkles/admin/routes/activities.php b/main/app/sprinkles/admin/routes/activities.php
new file mode 100755
index 0000000..b324553
--- /dev/null
+++ b/main/app/sprinkles/admin/routes/activities.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+/**
+ * Routes for administrative activity monitoring.
+ */
+$app->group('/activities', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\ActivityController:pageList')
+ ->setName('uri_activities');
+})->add('authGuard');
+
+$app->group('/api/activities', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\ActivityController:getList');
+})->add('authGuard');
diff --git a/main/app/sprinkles/admin/routes/admin.php b/main/app/sprinkles/admin/routes/admin.php
new file mode 100755
index 0000000..1a8c31a
--- /dev/null
+++ b/main/app/sprinkles/admin/routes/admin.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+/**
+ * Routes for administrative panel management.
+ */
+$app->group('/dashboard', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\AdminController:pageDashboard')
+ ->setName('dashboard');
+})->add('authGuard');
+
+$app->group('/api/dashboard', function () {
+ $this->post('/clear-cache', 'UserFrosting\Sprinkle\Admin\Controller\AdminController:clearCache');
+})->add('authGuard');
+
+$app->group('/modals/dashboard', function () {
+ $this->get('/clear-cache', 'UserFrosting\Sprinkle\Admin\Controller\AdminController:getModalConfirmClearCache');
+})->add('authGuard');
diff --git a/main/app/sprinkles/admin/routes/groups.php b/main/app/sprinkles/admin/routes/groups.php
new file mode 100755
index 0000000..e861960
--- /dev/null
+++ b/main/app/sprinkles/admin/routes/groups.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+/**
+ * Routes for administrative group management.
+ */
+$app->group('/groups', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:pageList')
+ ->setName('uri_groups');
+
+ $this->get('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:pageInfo');
+})->add('authGuard');
+
+$app->group('/api/groups', function () {
+ $this->delete('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:delete');
+
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getList');
+
+ $this->get('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getInfo');
+
+ $this->get('/g/{slug}/users', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getUsers');
+
+ $this->post('', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:create');
+
+ $this->put('/g/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:updateInfo');
+})->add('authGuard');
+
+$app->group('/modals/groups', function () {
+ $this->get('/confirm-delete', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getModalConfirmDelete');
+
+ $this->get('/create', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getModalCreate');
+
+ $this->get('/edit', 'UserFrosting\Sprinkle\Admin\Controller\GroupController:getModalEdit');
+})->add('authGuard');
diff --git a/main/app/sprinkles/admin/routes/permissions.php b/main/app/sprinkles/admin/routes/permissions.php
new file mode 100755
index 0000000..4df04c8
--- /dev/null
+++ b/main/app/sprinkles/admin/routes/permissions.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+/**
+ * Routes for administrative permission management.
+ */
+$app->group('/permissions', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:pageList')
+ ->setName('uri_permissions');
+
+ $this->get('/p/{id}', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:pageInfo');
+})->add('authGuard');
+
+$app->group('/api/permissions', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:getList');
+
+ $this->get('/p/{id}', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:getInfo');
+
+ $this->get('/p/{id}/users', 'UserFrosting\Sprinkle\Admin\Controller\PermissionController:getUsers');
+})->add('authGuard');
diff --git a/main/app/sprinkles/admin/routes/roles.php b/main/app/sprinkles/admin/routes/roles.php
new file mode 100755
index 0000000..1de12e8
--- /dev/null
+++ b/main/app/sprinkles/admin/routes/roles.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+/**
+ * Routes for administrative role management.
+ */
+$app->group('/roles', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:pageList')
+ ->setName('uri_roles');
+
+ $this->get('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:pageInfo');
+})->add('authGuard');
+
+$app->group('/api/roles', function () {
+ $this->delete('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:delete');
+
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getList');
+
+ $this->get('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getInfo');
+
+ $this->get('/r/{slug}/permissions', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getPermissions');
+
+ $this->get('/r/{slug}/users', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getUsers');
+
+ $this->post('', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:create');
+
+ $this->put('/r/{slug}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:updateInfo');
+
+ $this->put('/r/{slug}/{field}', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:updateField');
+})->add('authGuard');
+
+$app->group('/modals/roles', function () {
+ $this->get('/confirm-delete', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalConfirmDelete');
+
+ $this->get('/create', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalCreate');
+
+ $this->get('/edit', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalEdit');
+
+ $this->get('/permissions', 'UserFrosting\Sprinkle\Admin\Controller\RoleController:getModalEditPermissions');
+})->add('authGuard');
diff --git a/main/app/sprinkles/admin/routes/users.php b/main/app/sprinkles/admin/routes/users.php
new file mode 100755
index 0000000..f1b2243
--- /dev/null
+++ b/main/app/sprinkles/admin/routes/users.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+/**
+ * Routes for administrative user management.
+ */
+$app->group('/users', function () {
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\UserController:pageList')
+ ->setName('uri_users');
+
+ $this->get('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:pageInfo');
+})->add('authGuard');
+
+$app->group('/api/users', function () {
+ $this->delete('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:delete');
+
+ $this->get('', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getList');
+
+ $this->get('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getInfo');
+
+ $this->get('/u/{user_name}/activities', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getActivities');
+
+ $this->get('/u/{user_name}/roles', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getRoles');
+
+ $this->get('/u/{user_name}/permissions', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getPermissions');
+
+ $this->post('', 'UserFrosting\Sprinkle\Admin\Controller\UserController:create');
+
+ $this->post('/u/{user_name}/password-reset', 'UserFrosting\Sprinkle\Admin\Controller\UserController:createPasswordReset');
+
+ $this->put('/u/{user_name}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:updateInfo');
+
+ $this->put('/u/{user_name}/{field}', 'UserFrosting\Sprinkle\Admin\Controller\UserController:updateField');
+})->add('authGuard');
+
+$app->group('/modals/users', function () {
+ $this->get('/confirm-delete', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalConfirmDelete');
+
+ $this->get('/create', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalCreate');
+
+ $this->get('/edit', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalEdit');
+
+ $this->get('/password', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalEditPassword');
+
+ $this->get('/roles', 'UserFrosting\Sprinkle\Admin\Controller\UserController:getModalEditRoles');
+})->add('authGuard');
diff --git a/main/app/sprinkles/admin/schema/requests/group/create.yaml b/main/app/sprinkles/admin/schema/requests/group/create.yaml
new file mode 100755
index 0000000..8f5261c
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/group/create.yaml
@@ -0,0 +1,35 @@
+---
+name:
+ validators:
+ required:
+ label: "&NAME"
+ message: VALIDATE.REQUIRED
+ length:
+ label: "&NAME"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+slug:
+ validators:
+ required:
+ label: "&SLUG"
+ message: VALIDATE.REQUIRED
+ length:
+ label: "&SLUG"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+icon:
+ validators:
+ length:
+ label: "&ICON"
+ min: 1
+ max: 100
+ message: VALIDATE.LENGTH_RANGE
+ required:
+ message: VALIDATE.REQUIRED
+description:
diff --git a/main/app/sprinkles/admin/schema/requests/group/edit-info.yaml b/main/app/sprinkles/admin/schema/requests/group/edit-info.yaml
new file mode 100755
index 0000000..6aa3f28
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/group/edit-info.yaml
@@ -0,0 +1,27 @@
+---
+name:
+ validators:
+ length:
+ label: "&NAME"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+slug:
+ validators:
+ length:
+ label: "&SLUG"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+icon:
+ validators:
+ length:
+ label: "&ICON"
+ min: 1
+ max: 100
+ message: VALIDATE.LENGTH_RANGE
+description:
diff --git a/main/app/sprinkles/admin/schema/requests/group/get-by-slug.yaml b/main/app/sprinkles/admin/schema/requests/group/get-by-slug.yaml
new file mode 100755
index 0000000..2aa41b5
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/group/get-by-slug.yaml
@@ -0,0 +1,6 @@
+---
+slug:
+ validators:
+ required:
+ label: "&SLUG"
+ message: VALIDATE.REQUIRED
diff --git a/main/app/sprinkles/admin/schema/requests/role/create.yaml b/main/app/sprinkles/admin/schema/requests/role/create.yaml
new file mode 100755
index 0000000..8004184
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/role/create.yaml
@@ -0,0 +1,26 @@
+---
+name:
+ validators:
+ required:
+ label: "&NAME"
+ message: VALIDATE.REQUIRED
+ length:
+ label: "&NAME"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+slug:
+ validators:
+ required:
+ label: "&SLUG"
+ message: VALIDATE.REQUIRED
+ length:
+ label: "&SLUG"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+description:
diff --git a/main/app/sprinkles/admin/schema/requests/role/edit-field.yaml b/main/app/sprinkles/admin/schema/requests/role/edit-field.yaml
new file mode 100755
index 0000000..05c1b2d
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/role/edit-field.yaml
@@ -0,0 +1,24 @@
+---
+name:
+ validators:
+ length:
+ label: "&NAME"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+slug:
+ validators:
+ length:
+ label: "&SLUG"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+description:
+permissions:
+ validators:
+ array:
+ message: VALIDATE.ARRAY
diff --git a/main/app/sprinkles/admin/schema/requests/role/edit-info.yaml b/main/app/sprinkles/admin/schema/requests/role/edit-info.yaml
new file mode 100755
index 0000000..1fa36c8
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/role/edit-info.yaml
@@ -0,0 +1,20 @@
+---
+name:
+ validators:
+ length:
+ label: "&NAME"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+slug:
+ validators:
+ length:
+ label: "&SLUG"
+ min: 1
+ max: 255
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+description:
diff --git a/main/app/sprinkles/admin/schema/requests/role/get-by-slug.yaml b/main/app/sprinkles/admin/schema/requests/role/get-by-slug.yaml
new file mode 100755
index 0000000..2aa41b5
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/role/get-by-slug.yaml
@@ -0,0 +1,6 @@
+---
+slug:
+ validators:
+ required:
+ label: "&SLUG"
+ message: VALIDATE.REQUIRED
diff --git a/main/app/sprinkles/admin/schema/requests/user/create.yaml b/main/app/sprinkles/admin/schema/requests/user/create.yaml
new file mode 100755
index 0000000..7e575bc
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/user/create.yaml
@@ -0,0 +1,72 @@
+---
+user_name:
+ validators:
+ length:
+ label: "&USERNAME"
+ min: 1
+ max: 50
+ message: VALIDATE.LENGTH_RANGE
+ no_leading_whitespace:
+ label: "&USERNAME"
+ message: VALIDATE.NO_LEAD_WS
+ no_trailing_whitespace:
+ label: "&USERNAME"
+ message: VALIDATE.NO_TRAIL_WS
+ required:
+ label: "&USERNAME"
+ message: VALIDATE.REQUIRED
+ username:
+ label: "&USERNAME"
+ message: VALIDATE.USERNAME
+first_name:
+ validators:
+ length:
+ label: "&FIRST_NAME"
+ min: 1
+ max: 20
+ message: VALIDATE.LENGTH_RANGE
+ required:
+ label: "&FIRST_NAME"
+ message: VALIDATE.REQUIRED
+ transformations:
+ - trim
+last_name:
+ validators:
+ length:
+ label: "&LAST_NAME"
+ min: 1
+ max: 30
+ message: VALIDATE.LENGTH_RANGE
+ transformations:
+ - trim
+email:
+ validators:
+ required:
+ label: "&EMAIL"
+ message: VALIDATE.REQUIRED
+ length:
+ label: "&EMAIL"
+ min: 1
+ max: 150
+ message: VALIDATE.LENGTH_RANGE
+ email:
+ message: VALIDATE.INVALID_EMAIL
+locale:
+ default: en_US
+ validators:
+ required:
+ label: "&LOCALE"
+ domain: server
+ message: VALIDATE.REQUIRED
+ length:
+ label: "&LOCALE"
+ min: 1
+ max: 10
+ domain: server
+ message: VALIDATE.LENGTH_RANGE
+group_id:
+ validators:
+ integer:
+ label: "&GROUP"
+ domain: server
+ message: VALIDATE.INTEGER
diff --git a/main/app/sprinkles/admin/schema/requests/user/edit-field.yaml b/main/app/sprinkles/admin/schema/requests/user/edit-field.yaml
new file mode 100755
index 0000000..ab3b3aa
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/user/edit-field.yaml
@@ -0,0 +1,60 @@
+---
+first_name:
+ validators:
+ length:
+ label: "&FIRST_NAME"
+ min: 1
+ max: 20
+ message: VALIDATE.LENGTH_RANGE
+last_name:
+ validators:
+ length:
+ label: "&LAST_NAME"
+ min: 1
+ max: 30
+ message: VALIDATE.LENGTH_RANGE
+email:
+ validators:
+ length:
+ label: "&EMAIL"
+ min: 1
+ max: 150
+ message: VALIDATE.LENGTH_RANGE
+ email:
+ message: VALIDATE.INVALID_EMAIL
+locale:
+ validators:
+ length:
+ label: "&LOCALE"
+ min: 1
+ max: 10
+ message: VALIDATE.LENGTH_RANGE
+group_id:
+ validators:
+ integer:
+ message: VALIDATE.INTEGER
+flag_enabled:
+ validators:
+ member_of:
+ values:
+ - '0'
+ - '1'
+ message: VALIDATE.BOOLEAN
+flag_verified:
+ validators:
+ member_of:
+ values:
+ - '0'
+ - '1'
+ message: VALIDATE.BOOLEAN
+password:
+ validators:
+ length:
+ label: "&PASSWORD"
+ min: 12
+ max: 100
+ message: VALIDATE.LENGTH_RANGE
+roles:
+ validators:
+ array:
+ message: VALIDATE.ARRAY
diff --git a/main/app/sprinkles/admin/schema/requests/user/edit-info.yaml b/main/app/sprinkles/admin/schema/requests/user/edit-info.yaml
new file mode 100755
index 0000000..30ae920
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/user/edit-info.yaml
@@ -0,0 +1,36 @@
+---
+first_name:
+ validators:
+ length:
+ label: "&FIRST_NAME"
+ min: 1
+ max: 20
+ message: VALIDATE.LENGTH_RANGE
+last_name:
+ validators:
+ length:
+ label: "&LAST_NAME"
+ min: 1
+ max: 30
+ message: VALIDATE.LENGTH_RANGE
+email:
+ validators:
+ length:
+ label: "&EMAIL"
+ min: 1
+ max: 150
+ message: VALIDATE.LENGTH_RANGE
+ email:
+ message: VALIDATE.INVALID_EMAIL
+locale:
+ validators:
+ length:
+ label: "&LOCALE"
+ min: 1
+ max: 10
+ message: VALIDATE.LENGTH_RANGE
+group_id:
+ validators:
+ integer:
+ label: "&GROUP"
+ message: VALIDATE.INTEGER
diff --git a/main/app/sprinkles/admin/schema/requests/user/edit-password.yaml b/main/app/sprinkles/admin/schema/requests/user/edit-password.yaml
new file mode 100755
index 0000000..1d751ff
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/user/edit-password.yaml
@@ -0,0 +1,30 @@
+---
+value:
+ validators:
+ required:
+ domain: client
+ label: "&PASSWORD"
+ message: VALIDATE.REQUIRED
+ length:
+ domain: client
+ label: "&PASSWORD"
+ min: 12
+ max: 100
+ message: VALIDATE.LENGTH_RANGE
+passwordc:
+ validators:
+ required:
+ domain: client
+ label: "&PASSWORD.CONFIRM"
+ message: VALIDATE.REQUIRED
+ matches:
+ domain: client
+ field: value
+ label: "&PASSWORD.CONFIRM"
+ message: VALIDATE.PASSWORD_MISMATCH
+ length:
+ domain: client
+ label: "&PASSWORD.CONFIRM"
+ min: 12
+ max: 100
+ message: VALIDATE.LENGTH_RANGE
diff --git a/main/app/sprinkles/admin/schema/requests/user/get-by-username.yaml b/main/app/sprinkles/admin/schema/requests/user/get-by-username.yaml
new file mode 100755
index 0000000..97170dd
--- /dev/null
+++ b/main/app/sprinkles/admin/schema/requests/user/get-by-username.yaml
@@ -0,0 +1,6 @@
+---
+user_name:
+ validators:
+ required:
+ label: "&USERNAME"
+ message: VALIDATE.REQUIRED
diff --git a/main/app/sprinkles/admin/src/Admin.php b/main/app/sprinkles/admin/src/Admin.php
new file mode 100755
index 0000000..8a6dcc1
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Admin.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin;
+
+use UserFrosting\System\Sprinkle\Sprinkle;
+
+/**
+ * Bootstrapper class for the 'admin' sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Admin extends Sprinkle
+{
+
+}
diff --git a/main/app/sprinkles/admin/src/Controller/ActivityController.php b/main/app/sprinkles/admin/src/Controller/ActivityController.php
new file mode 100755
index 0000000..2fbe0d9
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Controller/ActivityController.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Controller;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\NotFoundException;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\ForbiddenException;
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Controller class for activity-related requests.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ActivityController extends SimpleController
+{
+ /**
+ * Returns a list of Activities
+ *
+ * Generates a list of activities, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getList($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_activities')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('activity_sprunje', $classMapper, $params);
+ $sprunje->extendQuery(function ($query) {
+ return $query->with('user');
+ });
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Renders the activity listing page.
+ *
+ * This page renders a table of user activities.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageList($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_activities')) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'pages/activities.html.twig');
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Controller/AdminController.php b/main/app/sprinkles/admin/src/Controller/AdminController.php
new file mode 100755
index 0000000..da4da8a
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Controller/AdminController.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Controller;
+
+use Carbon\Carbon;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Account\Database\Models\Group;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Account\Database\Models\Role;
+use UserFrosting\Sprinkle\Core\Database\Models\Version;
+use UserFrosting\Sprinkle\Core\Util\EnvironmentInfo;
+use UserFrosting\Support\Exception\ForbiddenException;
+
+/**
+ * AdminController Class
+ *
+ * Controller class for /dashboard URL. Handles admin-related activities
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AdminController extends SimpleController
+{
+
+ /**
+ * Renders the admin panel dashboard
+ *
+ */
+ public function pageDashboard($request, $response, $args)
+ {
+ //** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_dashboard')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Probably a better way to do this
+ $users = $classMapper->staticMethod('user', 'orderBy', 'created_at', 'desc')
+ ->take(8)
+ ->get();
+
+ // Transform the `create_at` date in "x days ago" type of string
+ $users->transform(function ($item, $key) {
+ $item->registered = Carbon::parse($item->created_at)->diffForHumans();
+ return $item;
+ });
+
+ /** @var Config $config */
+ $config = $this->ci->config;
+
+ /** @var Config $config */
+ $cache = $this->ci->cache;
+
+ // Get each sprinkle db version
+ $sprinkles = $this->ci->sprinkleManager->getSprinkleNames();
+
+ return $this->ci->view->render($response, 'pages/dashboard.html.twig', [
+ 'counter' => [
+ 'users' => $classMapper->staticMethod('user', 'count'),
+ 'roles' => $classMapper->staticMethod('role', 'count'),
+ 'groups' => $classMapper->staticMethod('group', 'count')
+ ],
+ 'info' => [
+ 'version' => [
+ 'UF' => \UserFrosting\VERSION,
+ 'php' => phpversion(),
+ 'database' => EnvironmentInfo::database()
+ ],
+ 'database' => [
+ 'name' => $config['db.default.database']
+ ],
+ 'environment' => $this->ci->environment,
+ 'path' => [
+ 'project' => \UserFrosting\ROOT_DIR
+ ]
+ ],
+ 'sprinkles' => $sprinkles,
+ 'users' => $users
+ ]);
+ }
+
+ /**
+ * Clear the site cache.
+ *
+ * This route requires authentication.
+ * Request type: POST
+ */
+ public function clearCache($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'clear_cache')) {
+ throw new ForbiddenException();
+ }
+
+ // Flush cache
+ $this->ci->cache->flush();
+
+ /** @var MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ $ms->addMessageTranslated('success', 'CACHE.CLEARED');
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Renders the modal form to confirm cache deletion.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalConfirmClearCache($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'clear_cache')) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'modals/confirm-clear-cache.html.twig', [
+ 'form' => [
+ 'action' => 'api/dashboard/clear-cache',
+ ]
+ ]);
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Controller/GroupController.php b/main/app/sprinkles/admin/src/Controller/GroupController.php
new file mode 100755
index 0000000..7ca94b1
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Controller/GroupController.php
@@ -0,0 +1,725 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Controller;
+
+use Carbon\Carbon;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\NotFoundException;
+use UserFrosting\Fortress\RequestDataTransformer;
+use UserFrosting\Fortress\RequestSchema;
+use UserFrosting\Fortress\ServerSideValidator;
+use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
+use UserFrosting\Sprinkle\Account\Database\Models\Group;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\ForbiddenException;
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Controller class for group-related requests, including listing groups, CRUD for groups, etc.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class GroupController extends SimpleController
+{
+ /**
+ * Processes the request to create a new group.
+ *
+ * Processes the request from the group creation form, checking that:
+ * 1. The group name and slug are not already in use;
+ * 2. The user has permission to create a new group;
+ * 3. The submitted data is valid.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: POST
+ * @see getModalCreateGroup
+ */
+ public function create($request, $response, $args)
+ {
+ // Get POST parameters: name, slug, icon, description
+ $params = $request->getParsedBody();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'create_group')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/group/create.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = false;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = true;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if name or slug already exists
+ if ($classMapper->staticMethod('group', 'where', 'name', $data['name'])->first()) {
+ $ms->addMessageTranslated('danger', 'GROUP.NAME.IN_USE', $data);
+ $error = true;
+ }
+
+ if ($classMapper->staticMethod('group', 'where', 'slug', $data['slug'])->first()) {
+ $ms->addMessageTranslated('danger', 'GROUP.SLUG.IN_USE', $data);
+ $error = true;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // All checks passed! log events/activities and create group
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $currentUser) {
+ // Create the group
+ $group = $classMapper->createInstance('group', $data);
+
+ // Store new group to database
+ $group->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} created group {$group->name}.", [
+ 'type' => 'group_create',
+ 'user_id' => $currentUser->id
+ ]);
+
+ $ms->addMessageTranslated('success', 'GROUP.CREATION_SUCCESSFUL', $data);
+ });
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes the request to delete an existing group.
+ *
+ * Deletes the specified group.
+ * Before doing so, checks that:
+ * 1. The user has permission to delete this group;
+ * 2. The group is not currently set as the default for new users;
+ * 3. The group is empty (does not have any users);
+ * 4. The submitted data is valid.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: DELETE
+ */
+ public function delete($request, $response, $args)
+ {
+ $group = $this->getGroupFromParams($args);
+
+ // If the group doesn't exist, return 404
+ if (!$group) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'delete_group', [
+ 'group' => $group
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Check that we are not deleting the default group
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ if ($group->slug == $config['site.registration.user_defaults.group']) {
+ $e = new BadRequestException();
+ $e->addUserMessage('GROUP.DELETE_DEFAULT', $group->toArray());
+ throw $e;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if there are any users in this group
+ $countGroupUsers = $classMapper->staticMethod('user', 'where', 'group_id', $group->id)->count();
+ if ($countGroupUsers > 0) {
+ $e = new BadRequestException();
+ $e->addUserMessage('GROUP.NOT_EMPTY', $group->toArray());
+ throw $e;
+ }
+
+ $groupName = $group->name;
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($group, $groupName, $currentUser) {
+ $group->delete();
+ unset($group);
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted group {$groupName}.", [
+ 'type' => 'group_delete',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ $ms->addMessageTranslated('success', 'GROUP.DELETION_SUCCESSFUL', [
+ 'name' => $groupName
+ ]);
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Returns info for a single group.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getInfo($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_groups')) {
+ throw new ForbiddenException();
+ }
+
+ $slug = $args['slug'];
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $group = $classMapper->staticMethod('group', 'where', 'slug', $slug)->first();
+
+ // If the group doesn't exist, return 404
+ if (!$group) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // Get group
+ $result = $group->toArray();
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response->withJson($result, 200, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Returns a list of Groups
+ *
+ * Generates a list of groups, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getList($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_groups')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('group_sprunje', $classMapper, $params);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ public function getModalConfirmDelete($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $group = $this->getGroupFromParams($params);
+
+ // If the group no longer exists, forward to main group listing page
+ if (!$group) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'delete_group', [
+ 'group' => $group
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if there are any users in this group
+ $countGroupUsers = $classMapper->staticMethod('user', 'where', 'group_id', $group->id)->count();
+ if ($countGroupUsers > 0) {
+ $e = new BadRequestException();
+ $e->addUserMessage('GROUP.NOT_EMPTY', $group->toArray());
+ throw $e;
+ }
+
+ return $this->ci->view->render($response, 'modals/confirm-delete-group.html.twig', [
+ 'group' => $group,
+ 'form' => [
+ 'action' => "api/groups/g/{$group->slug}",
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for creating a new group.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalCreate($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $translator = $this->ci->translator;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'create_group')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Create a dummy group to prepopulate fields
+ $group = $classMapper->createInstance('group', []);
+
+ $group->icon = 'fa fa-user';
+
+ $fieldNames = ['name', 'slug', 'icon', 'description'];
+ $fields = [
+ 'hidden' => [],
+ 'disabled' => []
+ ];
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/group/create.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'modals/group.html.twig', [
+ 'group' => $group,
+ 'form' => [
+ 'action' => 'api/groups',
+ 'method' => 'POST',
+ 'fields' => $fields,
+ 'submit_text' => $translator->translate('CREATE')
+ ],
+ 'page' => [
+ 'validators' => $validator->rules('json', false)
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for editing an existing group.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalEdit($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $group = $this->getGroupFromParams($params);
+
+ // If the group doesn't exist, return 404
+ if (!$group) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $translator = $this->ci->translator;
+
+ // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "icon", "description" for this group
+ $fieldNames = ['name', 'slug', 'icon', 'description'];
+ if (!$authorizer->checkAccess($currentUser, 'update_group_field', [
+ 'group' => $group,
+ 'fields' => $fieldNames
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Generate form
+ $fields = [
+ 'hidden' => [],
+ 'disabled' => []
+ ];
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/group/edit-info.yaml');
+ $validator = new JqueryValidationAdapter($schema, $translator);
+
+ return $this->ci->view->render($response, 'modals/group.html.twig', [
+ 'group' => $group,
+ 'form' => [
+ 'action' => "api/groups/g/{$group->slug}",
+ 'method' => 'PUT',
+ 'fields' => $fields,
+ 'submit_text' => $translator->translate('UPDATE')
+ ],
+ 'page' => [
+ 'validators' => $validator->rules('json', false)
+ ]
+ ]);
+ }
+
+ public function getUsers($request, $response, $args)
+ {
+ $group = $this->getGroupFromParams($args);
+
+ // If the group no longer exists, forward to main group listing page
+ if (!$group) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'view_group_field', [
+ 'group' => $group,
+ 'property' => 'users'
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params);
+ $sprunje->extendQuery(function ($query) use ($group) {
+ return $query->where('group_id', $group->id);
+ });
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Renders a page displaying a group's information, in read-only mode.
+ *
+ * This checks that the currently logged-in user has permission to view the requested group's info.
+ * It checks each field individually, showing only those that you have permission to view.
+ * This will also try to show buttons for deleting, and editing the group.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageInfo($request, $response, $args)
+ {
+ $group = $this->getGroupFromParams($args);
+
+ // If the group no longer exists, forward to main group listing page
+ if (!$group) {
+ $redirectPage = $this->ci->router->pathFor('uri_groups');
+ return $response->withRedirect($redirectPage, 404);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_group', [
+ 'group' => $group
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Determine fields that currentUser is authorized to view
+ $fieldNames = ['name', 'slug', 'icon', 'description'];
+
+ // Generate form
+ $fields = [
+ 'hidden' => []
+ ];
+
+ foreach ($fieldNames as $field) {
+ if (!$authorizer->checkAccess($currentUser, 'view_group_field', [
+ 'group' => $group,
+ 'property' => $field
+ ])) {
+ $fields['hidden'][] = $field;
+ }
+ }
+
+ // Determine buttons to display
+ $editButtons = [
+ 'hidden' => []
+ ];
+
+ if (!$authorizer->checkAccess($currentUser, 'update_group_field', [
+ 'group' => $group,
+ 'fields' => ['name', 'slug', 'icon', 'description']
+ ])) {
+ $editButtons['hidden'][] = 'edit';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'delete_group', [
+ 'group' => $group
+ ])) {
+ $editButtons['hidden'][] = 'delete';
+ }
+
+ return $this->ci->view->render($response, 'pages/group.html.twig', [
+ 'group' => $group,
+ 'fields' => $fields,
+ 'tools' => $editButtons
+ ]);
+ }
+
+ /**
+ * Renders the group listing page.
+ *
+ * This page renders a table of groups, with dropdown menus for admin actions for each group.
+ * Actions typically include: edit group, delete group.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageList($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_groups')) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'pages/groups.html.twig');
+ }
+
+ /**
+ * Processes the request to update an existing group's details.
+ *
+ * Processes the request from the group update form, checking that:
+ * 1. The group name/slug are not already in use;
+ * 2. The user has the necessary permissions to update the posted field(s);
+ * 3. The submitted data is valid.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: PUT
+ * @see getModalGroupEdit
+ */
+ public function updateInfo($request, $response, $args)
+ {
+ // Get the group based on slug in URL
+ $group = $this->getGroupFromParams($args);
+
+ if (!$group) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Get PUT parameters: (name, slug, icon, description)
+ $params = $request->getParsedBody();
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/group/edit-info.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = false;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = true;
+ }
+
+ // Determine targeted fields
+ $fieldNames = [];
+ foreach ($data as $name => $value) {
+ $fieldNames[] = $name;
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit submitted fields for this group
+ if (!$authorizer->checkAccess($currentUser, 'update_group_field', [
+ 'group' => $group,
+ 'fields' => array_values(array_unique($fieldNames))
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if name or slug already exists
+ if (
+ isset($data['name']) &&
+ $data['name'] != $group->name &&
+ $classMapper->staticMethod('group', 'where', 'name', $data['name'])->first()
+ ) {
+ $ms->addMessageTranslated('danger', 'GROUP.NAME.IN_USE', $data);
+ $error = true;
+ }
+
+ if (
+ isset($data['slug']) &&
+ $data['slug'] != $group->slug &&
+ $classMapper->staticMethod('group', 'where', 'slug', $data['slug'])->first()
+ ) {
+ $ms->addMessageTranslated('danger', 'GROUP.SLUG.IN_USE', $data);
+ $error = true;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($data, $group, $currentUser) {
+ // Update the group and generate success messages
+ foreach ($data as $name => $value) {
+ if ($value != $group->$name) {
+ $group->$name = $value;
+ }
+ }
+
+ $group->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated details for group {$group->name}.", [
+ 'type' => 'group_update_info',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ $ms->addMessageTranslated('success', 'GROUP.UPDATE', [
+ 'name' => $group->name
+ ]);
+
+ return $response->withStatus(200);
+ }
+
+ protected function getGroupFromParams($params)
+ {
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/group/get-by-slug.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and throw exception on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException
+ $e = new BadRequestException();
+ foreach ($validator->errors() as $idx => $field) {
+ foreach($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Get the group
+ $group = $classMapper->staticMethod('group', 'where', 'slug', $data['slug'])
+ ->first();
+
+ return $group;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Controller/PermissionController.php b/main/app/sprinkles/admin/src/Controller/PermissionController.php
new file mode 100755
index 0000000..f3e93ce
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Controller/PermissionController.php
@@ -0,0 +1,206 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Controller;
+
+use Carbon\Carbon;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\NotFoundException;
+use UserFrosting\Fortress\RequestDataTransformer;
+use UserFrosting\Fortress\RequestSchema;
+use UserFrosting\Fortress\ServerSideValidator;
+use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
+use UserFrosting\Sprinkle\Account\Database\Models\Permission;
+use UserFrosting\Sprinkle\Account\Database\Models\Role;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\ForbiddenException;
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Controller class for permission-related requests, including listing permissions, CRUD for permissions, etc.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PermissionController extends SimpleController
+{
+ /**
+ * Returns info for a single permission.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getInfo($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_permissions')) {
+ throw new ForbiddenException();
+ }
+
+ $permissionId = $args['id'];
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $permission = $classMapper->staticMethod('permission', 'find', $permissionId);
+
+ // If the permission doesn't exist, return 404
+ if (!$permission) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // Get permission
+ $result = $permission->load('users')->toArray();
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response->withJson($result, 200, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Returns a list of Permissions
+ *
+ * Generates a list of permissions, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getList($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_permissions')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('permission_sprunje', $classMapper, $params);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Returns a list of Users for a specified Permission.
+ *
+ * Generates a list of users, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getUsers($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_permissions')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $params['permission_id'] = $args['id'];
+
+ $sprunje = $classMapper->createInstance('permission_user_sprunje', $classMapper, $params);
+
+ $response = $sprunje->toResponse($response);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response;
+ }
+
+ /**
+ * Renders a page displaying a permission's information, in read-only mode.
+ *
+ * This checks that the currently logged-in user has permission to view permissions.
+ * Note that permissions cannot be modified through the interface. This is because
+ * permissions are highly coupled to the code and should only be modified by developers.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageInfo($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_permissions')) {
+ throw new ForbiddenException();
+ }
+
+ $permissionId = $args['id'];
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $permission = $classMapper->staticMethod('permission', 'find', $permissionId);
+
+ // If the permission doesn't exist, return 404
+ if (!$permission) {
+ throw new NotFoundException($request, $response);
+ }
+
+ return $this->ci->view->render($response, 'pages/permission.html.twig', [
+ 'permission' => $permission
+ ]);
+ }
+
+ /**
+ * Renders the permission listing page.
+ *
+ * This page renders a table of permissions, with dropdown menus for admin actions for each permission.
+ * Actions typically include: edit permission, delete permission.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageList($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_permissions')) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'pages/permissions.html.twig');
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Controller/RoleController.php b/main/app/sprinkles/admin/src/Controller/RoleController.php
new file mode 100755
index 0000000..ab86c88
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Controller/RoleController.php
@@ -0,0 +1,930 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Controller;
+
+use Carbon\Carbon;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\NotFoundException;
+use UserFrosting\Fortress\RequestDataTransformer;
+use UserFrosting\Fortress\RequestSchema;
+use UserFrosting\Fortress\ServerSideValidator;
+use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
+use UserFrosting\Sprinkle\Account\Database\Models\Role;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\ForbiddenException;
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Controller class for role-related requests, including listing roles, CRUD for roles, etc.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class RoleController extends SimpleController
+{
+ /**
+ * Processes the request to create a new role.
+ *
+ * Processes the request from the role creation form, checking that:
+ * 1. The role name and slug are not already in use;
+ * 2. The user has permission to create a new role;
+ * 3. The submitted data is valid.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: POST
+ * @see getModalCreateRole
+ */
+ public function create($request, $response, $args)
+ {
+ // Get POST parameters: name, slug, description
+ $params = $request->getParsedBody();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'create_role')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/role/create.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = false;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = true;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if name or slug already exists
+ if ($classMapper->staticMethod('role', 'where', 'name', $data['name'])->first()) {
+ $ms->addMessageTranslated('danger', 'ROLE.NAME_IN_USE', $data);
+ $error = true;
+ }
+
+ if ($classMapper->staticMethod('role', 'where', 'slug', $data['slug'])->first()) {
+ $ms->addMessageTranslated('danger', 'SLUG_IN_USE', $data);
+ $error = true;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // All checks passed! log events/activities and create role
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $currentUser) {
+ // Create the role
+ $role = $classMapper->createInstance('role', $data);
+
+ // Store new role to database
+ $role->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} created role {$role->name}.", [
+ 'type' => 'role_create',
+ 'user_id' => $currentUser->id
+ ]);
+
+ $ms->addMessageTranslated('success', 'ROLE.CREATION_SUCCESSFUL', $data);
+ });
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes the request to delete an existing role.
+ *
+ * Deletes the specified role.
+ * Before doing so, checks that:
+ * 1. The user has permission to delete this role;
+ * 2. The role is not a default for new users;
+ * 3. The role does not have any associated users;
+ * 4. The submitted data is valid.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: DELETE
+ */
+ public function delete($request, $response, $args)
+ {
+ $role = $this->getRoleFromParams($args);
+
+ // If the role doesn't exist, return 404
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'delete_role', [
+ 'role' => $role
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check that we are not deleting a default role
+ $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs');
+
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ if (in_array($role->slug, $defaultRoleSlugs)) {
+ $e = new BadRequestException();
+ $e->addUserMessage('ROLE.DELETE_DEFAULT');
+ throw $e;
+ }
+
+ // Check if there are any users associated with this role
+ $countUsers = $role->users()->count();
+ if ($countUsers > 0) {
+ $e = new BadRequestException();
+ $e->addUserMessage('ROLE.HAS_USERS');
+ throw $e;
+ }
+
+ $roleName = $role->name;
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($role, $roleName, $currentUser) {
+ $role->delete();
+ unset($role);
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted role {$roleName}.", [
+ 'type' => 'role_delete',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ $ms->addMessageTranslated('success', 'ROLE.DELETION_SUCCESSFUL', [
+ 'name' => $roleName
+ ]);
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Returns info for a single role, along with associated permissions.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getInfo($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_roles')) {
+ throw new ForbiddenException();
+ }
+
+ $slug = $args['slug'];
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $role = $classMapper->staticMethod('role', 'where', 'slug', $slug)->first();
+
+ // If the role doesn't exist, return 404
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // Get role
+ $result = $role->load('permissions')->toArray();
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response->withJson($result, 200, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Returns a list of Roles
+ *
+ * Generates a list of roles, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getList($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_roles')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('role_sprunje', $classMapper, $params);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ public function getModalConfirmDelete($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $role = $this->getRoleFromParams($params);
+
+ // If the role no longer exists, forward to main role listing page
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'delete_role', [
+ 'role' => $role
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check that we are not deleting a default role
+ $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs');
+
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ if (in_array($role->slug, $defaultRoleSlugs)) {
+ $e = new BadRequestException();
+ $e->addUserMessage('ROLE.DELETE_DEFAULT', $role->toArray());
+ throw $e;
+ }
+
+ // Check if there are any users associated with this role
+ $countUsers = $role->users()->count();
+ if ($countUsers > 0) {
+ $e = new BadRequestException();
+ $e->addUserMessage('ROLE.HAS_USERS', $role->toArray());
+ throw $e;
+ }
+
+ return $this->ci->view->render($response, 'modals/confirm-delete-role.html.twig', [
+ 'role' => $role,
+ 'form' => [
+ 'action' => "api/roles/r/{$role->slug}",
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for creating a new role.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalCreate($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $translator = $this->ci->translator;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'create_role')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Create a dummy role to prepopulate fields
+ $role = $classMapper->createInstance('role', []);
+
+ $fieldNames = ['name', 'slug', 'description'];
+ $fields = [
+ 'hidden' => [],
+ 'disabled' => []
+ ];
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/role/create.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'modals/role.html.twig', [
+ 'role' => $role,
+ 'form' => [
+ 'action' => 'api/roles',
+ 'method' => 'POST',
+ 'fields' => $fields,
+ 'submit_text' => $translator->translate('CREATE')
+ ],
+ 'page' => [
+ 'validators' => $validator->rules('json', false)
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for editing an existing role.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalEdit($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $role = $this->getRoleFromParams($params);
+
+ // If the role doesn't exist, return 404
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $translator = $this->ci->translator;
+
+ // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "description" for this role
+ $fieldNames = ['name', 'slug', 'description'];
+ if (!$authorizer->checkAccess($currentUser, 'update_role_field', [
+ 'role' => $role,
+ 'fields' => $fieldNames
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Generate form
+ $fields = [
+ 'hidden' => [],
+ 'disabled' => []
+ ];
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/role/edit-info.yaml');
+ $validator = new JqueryValidationAdapter($schema, $translator);
+
+ return $this->ci->view->render($response, 'modals/role.html.twig', [
+ 'role' => $role,
+ 'form' => [
+ 'action' => "api/roles/r/{$role->slug}",
+ 'method' => 'PUT',
+ 'fields' => $fields,
+ 'submit_text' => $translator->translate('UPDATE')
+ ],
+ 'page' => [
+ 'validators' => $validator->rules('json', false)
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for editing a role's permissions.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalEditPermissions($request, $response, $args)
+ {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $role = $this->getRoleFromParams($params);
+
+ // If the role doesn't exist, return 404
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit "permissions" field for this role
+ if (!$authorizer->checkAccess($currentUser, 'update_role_field', [
+ 'role' => $role,
+ 'fields' => ['permissions']
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'modals/role-manage-permissions.html.twig', [
+ 'role' => $role
+ ]);
+ }
+
+ /**
+ * Returns a list of Permissions for a specified Role.
+ *
+ * Generates a list of permissions, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getPermissions($request, $response, $args)
+ {
+ $role = $this->getRoleFromParams($args);
+
+ // If the role no longer exists, forward to main role listing page
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'view_role_field', [
+ 'role' => $role,
+ 'property' => 'permissions'
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('permission_sprunje', $classMapper, $params);
+ $sprunje->extendQuery(function ($query) use ($role) {
+ return $query->forRole($role->id);
+ });
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Returns users associated with a single role.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getUsers($request, $response, $args)
+ {
+ $role = $this->getRoleFromParams($args);
+
+ // If the role doesn't exist, return 404
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'view_role_field', [
+ 'role' => $role,
+ 'property' => 'users'
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ $sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params);
+ $sprunje->extendQuery(function ($query) use ($role) {
+ return $query->forRole($role->id);
+ });
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Renders a page displaying a role's information, in read-only mode.
+ *
+ * This checks that the currently logged-in user has permission to view the requested role's info.
+ * It checks each field individually, showing only those that you have permission to view.
+ * This will also try to show buttons for deleting and editing the role.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageInfo($request, $response, $args)
+ {
+ $role = $this->getRoleFromParams($args);
+
+ // If the role no longer exists, forward to main role listing page
+ if (!$role) {
+ $redirectPage = $this->ci->router->pathFor('uri_roles');
+ return $response->withRedirect($redirectPage, 404);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_role', [
+ 'role' => $role
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Determine fields that currentUser is authorized to view
+ $fieldNames = ['name', 'slug', 'description'];
+
+ // Generate form
+ $fields = [
+ 'hidden' => []
+ ];
+
+ foreach ($fieldNames as $field) {
+ if (!$authorizer->checkAccess($currentUser, 'view_role_field', [
+ 'role' => $role,
+ 'property' => $field
+ ])) {
+ $fields['hidden'][] = $field;
+ }
+ }
+
+ // Determine buttons to display
+ $editButtons = [
+ 'hidden' => []
+ ];
+
+ if (!$authorizer->checkAccess($currentUser, 'update_role_field', [
+ 'role' => $role,
+ 'fields' => ['name', 'slug', 'description']
+ ])) {
+ $editButtons['hidden'][] = 'edit';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'delete_role', [
+ 'role' => $role
+ ])) {
+ $editButtons['hidden'][] = 'delete';
+ }
+
+ return $this->ci->view->render($response, 'pages/role.html.twig', [
+ 'role' => $role,
+ 'fields' => $fields,
+ 'tools' => $editButtons
+ ]);
+ }
+
+ /**
+ * Renders the role listing page.
+ *
+ * This page renders a table of roles, with dropdown menus for admin actions for each role.
+ * Actions typically include: edit role, delete role.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageList($request, $response, $args)
+ {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_roles')) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'pages/roles.html.twig');
+ }
+
+ /**
+ * Processes the request to update an existing role's details.
+ *
+ * Processes the request from the role update form, checking that:
+ * 1. The role name/slug are not already in use;
+ * 2. The user has the necessary permissions to update the posted field(s);
+ * 3. The submitted data is valid.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: PUT
+ * @see getModalRoleEdit
+ */
+ public function updateInfo($request, $response, $args)
+ {
+ // Get the role based on slug in the URL
+ $role = $this->getRoleFromParams($args);
+
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Get PUT parameters: (name, slug, description)
+ $params = $request->getParsedBody();
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $ms = $this->ci->alerts;
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/role/edit-info.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = false;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = true;
+ }
+
+ // Determine targeted fields
+ $fieldNames = [];
+ foreach ($data as $name => $value) {
+ $fieldNames[] = $name;
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit submitted fields for this role
+ if (!$authorizer->checkAccess($currentUser, 'update_role_field', [
+ 'role' => $role,
+ 'fields' => array_values(array_unique($fieldNames))
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if name or slug already exists
+ if (
+ isset($data['name']) &&
+ $data['name'] != $role->name &&
+ $classMapper->staticMethod('role', 'where', 'name', $data['name'])->first()
+ ) {
+ $ms->addMessageTranslated('danger', 'ROLE.NAME_IN_USE', $data);
+ $error = true;
+ }
+
+ if (
+ isset($data['slug']) &&
+ $data['slug'] != $role->slug &&
+ $classMapper->staticMethod('role', 'where', 'slug', $data['slug'])->first()
+ ) {
+ $ms->addMessageTranslated('danger', 'SLUG_IN_USE', $data);
+ $error = true;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($data, $role, $currentUser) {
+ // Update the role and generate success messages
+ foreach ($data as $name => $value) {
+ if ($value != $role->$name){
+ $role->$name = $value;
+ }
+ }
+
+ $role->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated details for role {$role->name}.", [
+ 'type' => 'role_update_info',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ $ms->addMessageTranslated('success', 'ROLE.UPDATED', [
+ 'name' => $role->name
+ ]);
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes the request to update a specific field for an existing role, including permissions.
+ *
+ * Processes the request from the role update form, checking that:
+ * 1. The logged-in user has the necessary permissions to update the putted field(s);
+ * 2. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: PUT
+ */
+ public function updateField($request, $response, $args)
+ {
+ // Get the username from the URL
+ $role = $this->getRoleFromParams($args);
+
+ if (!$role) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // Get key->value pair from URL and request body
+ $fieldName = $args['field'];
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit the specified field for this user
+ if (!$authorizer->checkAccess($currentUser, 'update_role_field', [
+ 'role' => $role,
+ 'fields' => [$fieldName]
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Get PUT parameters: value
+ $put = $request->getParsedBody();
+
+ if (!isset($put['value'])) {
+ throw new BadRequestException();
+ }
+
+ $params = [
+ $fieldName => $put['value']
+ ];
+
+ // Validate key -> value pair
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/role/edit-field.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and throw exception on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException
+ $e = new BadRequestException();
+ foreach ($validator->errors() as $idx => $field) {
+ foreach($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ // Get validated and transformed value
+ $fieldValue = $data[$fieldName];
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $ms = $this->ci->alerts;
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction( function() use ($fieldName, $fieldValue, $role, $currentUser) {
+ if ($fieldName == 'permissions') {
+ $newPermissions = collect($fieldValue)->pluck('permission_id')->all();
+ $role->permissions()->sync($newPermissions);
+ } else {
+ $role->$fieldName = $fieldValue;
+ $role->save();
+ }
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated property '$fieldName' for role {$role->name}.", [
+ 'type' => 'role_update_field',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ // Add success messages
+ if ($fieldName == 'permissions') {
+ $ms->addMessageTranslated('success', 'ROLE.PERMISSIONS_UPDATED', [
+ 'name' => $role->name
+ ]);
+ } else {
+ $ms->addMessageTranslated('success', 'ROLE.UPDATED', [
+ 'name' => $role->name
+ ]);
+ }
+
+ return $response->withStatus(200);
+ }
+
+ protected function getRoleFromParams($params)
+ {
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/role/get-by-slug.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and throw exception on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException
+ $e = new BadRequestException();
+ foreach ($validator->errors() as $idx => $field) {
+ foreach($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Get the role
+ $role = $classMapper->staticMethod('role', 'where', 'slug', $data['slug'])
+ ->first();
+
+ return $role;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Controller/UserController.php b/main/app/sprinkles/admin/src/Controller/UserController.php
new file mode 100755
index 0000000..5bece6a
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Controller/UserController.php
@@ -0,0 +1,1261 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Admin\Controller;
+
+use Carbon\Carbon;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\NotFoundException;
+use UserFrosting\Fortress\RequestDataTransformer;
+use UserFrosting\Fortress\RequestSchema;
+use UserFrosting\Fortress\ServerSideValidator;
+use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
+use UserFrosting\Sprinkle\Account\Database\Models\Group;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Account\Facades\Password;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Mail\EmailRecipient;
+use UserFrosting\Sprinkle\Core\Mail\TwigMailMessage;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\ForbiddenException;
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Controller class for user-related requests, including listing users, CRUD for users, etc.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class UserController extends SimpleController
+{
+ /**
+ * Processes the request to create a new user (from the admin controls).
+ *
+ * Processes the request from the user creation form, checking that:
+ * 1. The username and email are not already in use;
+ * 2. The logged-in user has the necessary permissions to update the posted field(s);
+ * 3. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: POST
+ * @see getModalCreate
+ */
+ public function create($request, $response, $args) {
+ // Get POST parameters: user_name, first_name, last_name, email, locale, (group)
+ $params = $request->getParsedBody();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'create_user')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/user/create.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = FALSE;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = TRUE;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if username or email already exists
+ if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) {
+ $ms->addMessageTranslated('danger', 'USERNAME.IN_USE', $data);
+ $error = TRUE;
+ }
+
+ if ($classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) {
+ $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data);
+ $error = TRUE;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // If currentUser does not have permission to set the group, but they try to set it to something other than their own group,
+ // throw an exception.
+ if (!$authorizer->checkAccess($currentUser, 'create_user_field', [
+ 'fields' => ['group']
+ ])) {
+ if (isset($data['group_id']) && $data['group_id'] != $currentUser->group_id) {
+ throw new ForbiddenException();
+ }
+ }
+
+ // In any case, set the group id if not otherwise set
+ if (!isset($data['group_id'])) {
+ $data['group_id'] = $currentUser->group_id;
+ }
+
+ $data['flag_verified'] = 1;
+ // Set password as empty on initial creation. We will then send email so new user can set it themselves via a verification token
+ $data['password'] = '';
+
+ // All checks passed! log events/activities, create user, and send verification email (if required)
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $currentUser) {
+ // Create the user
+ $user = $classMapper->createInstance('user', $data);
+
+ // Store new user to database
+ $user->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} created a new account for {$user->user_name}.", [
+ 'type' => 'account_create',
+ 'user_id' => $currentUser->id
+ ]);
+
+ // Load default roles
+ $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs');
+ $defaultRoles = $classMapper->staticMethod('role', 'whereIn', 'slug', $defaultRoleSlugs)->get();
+ $defaultRoleIds = $defaultRoles->pluck('id')->all();
+
+ // Attach default roles
+ $user->roles()->attach($defaultRoleIds);
+
+ // Try to generate a new password request
+ $passwordRequest = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.create']);
+
+ // Create and send welcome email with password set link
+ $message = new TwigMailMessage($this->ci->view, 'mail/password-create.html.twig');
+
+ $message->from($config['address_book.admin'])
+ ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
+ ->addParams([
+ 'user' => $user,
+ 'create_password_expiration' => $config['password_reset.timeouts.create'] / 3600 . ' hours',
+ 'token' => $passwordRequest->getToken()
+ ]);
+
+ $this->ci->mailer->send($message);
+
+ $ms->addMessageTranslated('success', 'USER.CREATED', $data);
+ });
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes the request to send a user a password reset email.
+ *
+ * Processes the request from the user update form, checking that:
+ * 1. The target user's new email address, if specified, is not already in use;
+ * 2. The logged-in user has the necessary permissions to update the posted field(s);
+ * 3. We're not trying to disable the master account;
+ * 4. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: POST
+ */
+ public function createPasswordReset($request, $response, $args) {
+ // Get the username from the URL
+ $user = $this->getUserFromParams($args);
+
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit "password" for this user
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['password']
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($user, $config) {
+
+ // Create a password reset and shoot off an email
+ $passwordReset = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.reset']);
+
+ // Create and send welcome email with password set link
+ $message = new TwigMailMessage($this->ci->view, 'mail/password-reset.html.twig');
+
+ $message->from($config['address_book.admin'])
+ ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
+ ->addParams([
+ 'user' => $user,
+ 'token' => $passwordReset->getToken(),
+ 'request_date' => Carbon::now()->format('Y-m-d H:i:s')
+ ]);
+
+ $this->ci->mailer->send($message);
+ });
+
+ $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_SENT', [
+ 'email' => $user->email
+ ]);
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes the request to delete an existing user.
+ *
+ * Deletes the specified user, removing any existing associations.
+ * Before doing so, checks that:
+ * 1. You are not trying to delete the master account;
+ * 2. You have permission to delete the target user's account.
+ * This route requires authentication (and should generally be limited to admins or the root user).
+ * Request type: DELETE
+ */
+ public function delete($request, $response, $args) {
+ $user = $this->getUserFromParams($args);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'delete_user', [
+ 'user' => $user
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Check that we are not deleting the master account
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ if ($user->id == $config['reserved_user_ids.master']) {
+ $e = new BadRequestException();
+ $e->addUserMessage('DELETE_MASTER');
+ throw $e;
+ }
+
+ $userName = $user->user_name;
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($user, $userName, $currentUser) {
+ $user->delete();
+ unset($user);
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted the account for {$userName}.", [
+ 'type' => 'account_delete',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ $ms->addMessageTranslated('success', 'DELETION_SUCCESSFUL', [
+ 'user_name' => $userName
+ ]);
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Returns activity history for a single user.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getActivities($request, $response, $args) {
+ $user = $this->getUserFromParams($args);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'view_user_field', [
+ 'user' => $user,
+ 'property' => 'activities'
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ $sprunje = $classMapper->createInstance('activity_sprunje', $classMapper, $params);
+
+ $sprunje->extendQuery(function ($query) use ($user) {
+ return $query->where('user_id', $user->id);
+ });
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Returns info for a single user.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getInfo($request, $response, $args) {
+ $user = $this->getUserFromParams($args);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Join user's most recent activity
+ $user = $classMapper->createInstance('user')
+ ->where('user_name', $user->user_name)
+ ->joinLastActivity()
+ ->with('lastActivity', 'group')
+ ->first();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_user', [
+ 'user' => $user
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ $result = $user->toArray();
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response->withJson($result, 200, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Returns a list of Users
+ *
+ * Generates a list of users, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getList($request, $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_users')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Renders the modal form to confirm user deletion.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalConfirmDelete($request, $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $user = $this->getUserFromParams($params);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'delete_user', [
+ 'user' => $user
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Check that we are not deleting the master account
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ if ($user->id == $config['reserved_user_ids.master']) {
+ $e = new BadRequestException();
+ $e->addUserMessage('DELETE_MASTER');
+ throw $e;
+ }
+
+ return $this->ci->view->render($response, 'modals/confirm-delete-user.html.twig', [
+ 'user' => $user,
+ 'form' => [
+ 'action' => "api/users/u/{$user->user_name}",
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for creating a new user.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * If the currently logged-in user has permission to modify user group membership, then the group toggle will be displayed.
+ * Otherwise, the user will be added to the default group and receive the default roles automatically.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalCreate($request, $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ /** @var UserFrosting\I18n\MessageTranslator $translator */
+ $translator = $this->ci->translator;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'create_user')) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Determine form fields to hide/disable
+ // TODO: come back to this when we finish implementing theming
+ $fields = [
+ 'hidden' => ['theme'],
+ 'disabled' => []
+ ];
+
+ // Get a list of all locales
+ $locales = $config->getDefined('site.locales.available');
+
+ // Determine if currentUser has permission to modify the group. If so, show the 'group' dropdown.
+ // Otherwise, set to the currentUser's group and disable the dropdown.
+ if ($authorizer->checkAccess($currentUser, 'create_user_field', [
+ 'fields' => ['group']
+ ])) {
+ // Get a list of all groups
+ $groups = $classMapper->staticMethod('group', 'all');
+ } else {
+ // Get the current user's group
+ $groups = $currentUser->group()->get();
+ $fields['disabled'][] = 'group';
+ }
+
+ // Create a dummy user to prepopulate fields
+ $data = [
+ 'group_id' => $currentUser->group_id,
+ 'locale' => $config['site.registration.user_defaults.locale'],
+ 'theme' => ''
+ ];
+
+ $user = $classMapper->createInstance('user', $data);
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/user/create.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'modals/user.html.twig', [
+ 'user' => $user,
+ 'groups' => $groups,
+ 'locales' => $locales,
+ 'form' => [
+ 'action' => 'api/users',
+ 'method' => 'POST',
+ 'fields' => $fields,
+ 'submit_text' => $translator->translate('CREATE')
+ ],
+ 'page' => [
+ 'validators' => $validator->rules('json', FALSE)
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for editing an existing user.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalEdit($request, $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $user = $this->getUserFromParams($params);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Get the user to edit
+ $user = $classMapper->staticMethod('user', 'where', 'user_name', $user->user_name)
+ ->with('group')
+ ->first();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "email", "locale" for this user
+ $fieldNames = ['name', 'email', 'locale'];
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => $fieldNames
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Get a list of all groups
+ $groups = $classMapper->staticMethod('group', 'all');
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Get a list of all locales
+ $locales = $config->getDefined('site.locales.available');
+
+ // Generate form
+ $fields = [
+ 'hidden' => ['theme'],
+ 'disabled' => ['user_name']
+ ];
+
+ // Disable group field if currentUser doesn't have permission to modify group
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['group']
+ ])) {
+ $fields['disabled'][] = 'group';
+ }
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/user/edit-info.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ $translator = $this->ci->translator;
+
+ return $this->ci->view->render($response, 'modals/user.html.twig', [
+ 'user' => $user,
+ 'groups' => $groups,
+ 'locales' => $locales,
+ 'form' => [
+ 'action' => "api/users/u/{$user->user_name}",
+ 'method' => 'PUT',
+ 'fields' => $fields,
+ 'submit_text' => $translator->translate('UPDATE')
+ ],
+ 'page' => [
+ 'validators' => $validator->rules('json', FALSE)
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for editing a user's password.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalEditPassword($request, $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $user = $this->getUserFromParams($params);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit "password" field for this user
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['password']
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/user/edit-password.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'modals/user-set-password.html.twig', [
+ 'user' => $user,
+ 'page' => [
+ 'validators' => $validator->rules('json', FALSE)
+ ]
+ ]);
+ }
+
+ /**
+ * Renders the modal form for editing a user's roles.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getModalEditRoles($request, $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ $user = $this->getUserFromParams($params);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit "roles" field for this user
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['roles']
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'modals/user-manage-roles.html.twig', [
+ 'user' => $user
+ ]);
+ }
+
+ /**
+ * Returns a list of effective Permissions for a specified User.
+ *
+ * Generates a list of permissions, optionally paginated, sorted and/or filtered.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getPermissions($request, $response, $args) {
+ $user = $this->getUserFromParams($args);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'view_user_field', [
+ 'user' => $user,
+ 'property' => 'permissions'
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $params['user_id'] = $user->id;
+ $sprunje = $classMapper->createInstance('user_permission_sprunje', $classMapper, $params);
+
+ $response = $sprunje->toResponse($response);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response;
+ }
+
+ /**
+ * Returns roles associated with a single user.
+ *
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function getRoles($request, $response, $args) {
+ $user = $this->getUserFromParams($args);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'view_user_field', [
+ 'user' => $user,
+ 'property' => 'roles'
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ $sprunje = $classMapper->createInstance('role_sprunje', $classMapper, $params);
+ $sprunje->extendQuery(function ($query) use ($user) {
+ return $query->forUser($user->id);
+ });
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $sprunje->toResponse($response);
+ }
+
+ /**
+ * Renders a page displaying a user's information, in read-only mode.
+ *
+ * This checks that the currently logged-in user has permission to view the requested user's info.
+ * It checks each field individually, showing only those that you have permission to view.
+ * This will also try to show buttons for activating, disabling/enabling, deleting, and editing the user.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageInfo($request, $response, $args) {
+ $user = $this->getUserFromParams($args);
+
+ // If the user no longer exists, forward to main user listing page
+ if (!$user) {
+ $usersPage = $this->ci->router->pathFor('uri_users');
+ return $response->withRedirect($usersPage, 404);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_user', [
+ 'user' => $user
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Get a list of all locales
+ $locales = $config->getDefined('site.locales.available');
+
+ // Determine fields that currentUser is authorized to view
+ $fieldNames = ['user_name', 'name', 'email', 'locale', 'group', 'roles'];
+
+ // Generate form
+ $fields = [
+ // Always hide these
+ 'hidden' => ['theme']
+ ];
+
+ // Determine which fields should be hidden
+ foreach ($fieldNames as $field) {
+ if (!$authorizer->checkAccess($currentUser, 'view_user_field', [
+ 'user' => $user,
+ 'property' => $field
+ ])) {
+ $fields['hidden'][] = $field;
+ }
+ }
+
+ // Determine buttons to display
+ $editButtons = [
+ 'hidden' => []
+ ];
+
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['name', 'email', 'locale']
+ ])) {
+ $editButtons['hidden'][] = 'edit';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['flag_enabled']
+ ])) {
+ $editButtons['hidden'][] = 'enable';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['flag_verified']
+ ])) {
+ $editButtons['hidden'][] = 'activate';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['password']
+ ])) {
+ $editButtons['hidden'][] = 'password';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => ['roles']
+ ])) {
+ $editButtons['hidden'][] = 'roles';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'delete_user', [
+ 'user' => $user
+ ])) {
+ $editButtons['hidden'][] = 'delete';
+ }
+
+ // Determine widgets to display
+ $widgets = [
+ 'hidden' => []
+ ];
+
+ if (!$authorizer->checkAccess($currentUser, 'view_user_field', [
+ 'user' => $user,
+ 'property' => 'permissions'
+ ])) {
+ $widgets['hidden'][] = 'permissions';
+ }
+
+ if (!$authorizer->checkAccess($currentUser, 'view_user_field', [
+ 'user' => $user,
+ 'property' => 'activities'
+ ])) {
+ $widgets['hidden'][] = 'activities';
+ }
+
+ return $this->ci->view->render($response, 'pages/user.html.twig', [
+ 'user' => $user,
+ 'locales' => $locales,
+ 'fields' => $fields,
+ 'tools' => $editButtons,
+ 'widgets' => $widgets
+ ]);
+ }
+
+ /**
+ * Renders the user listing page.
+ *
+ * This page renders a table of users, with dropdown menus for admin actions for each user.
+ * Actions typically include: edit user details, activate user, enable/disable user, delete user.
+ * This page requires authentication.
+ * Request type: GET
+ */
+ public function pageList($request, $response, $args) {
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_users')) {
+ throw new ForbiddenException();
+ }
+
+ return $this->ci->view->render($response, 'pages/users.html.twig');
+ }
+
+ /**
+ * Processes the request to update an existing user's basic details (first_name, last_name, email, locale, group_id)
+ *
+ * Processes the request from the user update form, checking that:
+ * 1. The target user's new email address, if specified, is not already in use;
+ * 2. The logged-in user has the necessary permissions to update the putted field(s);
+ * 3. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: PUT
+ */
+ public function updateInfo($request, $response, $args) {
+ // Get the username from the URL
+ $user = $this->getUserFromParams($args);
+
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Get PUT parameters
+ $params = $request->getParsedBody();
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/user/edit-info.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = FALSE;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = TRUE;
+ }
+
+ // Determine targeted fields
+ $fieldNames = [];
+ foreach ($data as $name => $value) {
+ if ($name == 'first_name' || $name == 'last_name') {
+ $fieldNames[] = 'name';
+ } elseif ($name == 'group_id') {
+ $fieldNames[] = 'group';
+ } else {
+ $fieldNames[] = $name;
+ }
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit submitted fields for this user
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => array_values(array_unique($fieldNames))
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ // Only the master account can edit the master account!
+ if (
+ ($user->id == $config['reserved_user_ids.master']) &&
+ ($currentUser->id != $config['reserved_user_ids.master'])
+ ) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Check if email already exists
+ if (
+ isset($data['email']) &&
+ $data['email'] != $user->email &&
+ $classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')
+ ) {
+ $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data);
+ $error = TRUE;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($data, $user, $currentUser) {
+ // Update the user and generate success messages
+ foreach ($data as $name => $value) {
+ if ($value != $user->$name) {
+ $user->$name = $value;
+ }
+ }
+
+ $user->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated basic account info for user {$user->user_name}.", [
+ 'type' => 'account_update_info',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ $ms->addMessageTranslated('success', 'DETAILS_UPDATED', [
+ 'user_name' => $user->user_name
+ ]);
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes the request to update a specific field for an existing user.
+ *
+ * Supports editing all user fields, including password, enabled/disabled status and verification status.
+ * Processes the request from the user update form, checking that:
+ * 1. The logged-in user has the necessary permissions to update the putted field(s);
+ * 2. We're not trying to disable the master account;
+ * 3. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: PUT
+ */
+ public function updateField($request, $response, $args) {
+ // Get the username from the URL
+ $user = $this->getUserFromParams($args);
+
+ if (!$user) {
+ throw new NotFoundException($request, $response);
+ }
+
+ // Get key->value pair from URL and request body
+ $fieldName = $args['field'];
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled resource - check that currentUser has permission to edit the specified field for this user
+ if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
+ 'user' => $user,
+ 'fields' => [$fieldName]
+ ])) {
+ throw new ForbiddenException();
+ }
+
+ /** @var UserFrosting\Config\Config $config */
+ $config = $this->ci->config;
+
+ // Only the master account can edit the master account!
+ if (
+ ($user->id == $config['reserved_user_ids.master']) &&
+ ($currentUser->id != $config['reserved_user_ids.master'])
+ ) {
+ throw new ForbiddenException();
+ }
+
+ // Get PUT parameters: value
+ $put = $request->getParsedBody();
+
+ if (!isset($put['value'])) {
+ throw new BadRequestException();
+ }
+
+ // Create and validate key -> value pair
+ $params = [
+ $fieldName => $put['value']
+ ];
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/user/edit-field.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and throw exception on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException
+ $e = new BadRequestException();
+ foreach ($validator->errors() as $idx => $field) {
+ foreach ($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ // Get validated and transformed value
+ $fieldValue = $data[$fieldName];
+
+ /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */
+ $ms = $this->ci->alerts;
+
+ // Special checks and transformations for certain fields
+ if ($fieldName == 'flag_enabled') {
+ // Check that we are not disabling the master account
+ if (
+ ($user->id == $config['reserved_user_ids.master']) &&
+ ($fieldValue == '0')
+ ) {
+ $e = new BadRequestException();
+ $e->addUserMessage('DISABLE_MASTER');
+ throw $e;
+ } elseif (
+ ($user->id == $currentUser->id) &&
+ ($fieldValue == '0')
+ ) {
+ $e = new BadRequestException();
+ $e->addUserMessage('DISABLE_SELF');
+ throw $e;
+ }
+ } elseif ($fieldName == 'password') {
+ $fieldValue = Password::hash($fieldValue);
+ }
+
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($fieldName, $fieldValue, $user, $currentUser) {
+ if ($fieldName == 'roles') {
+ $newRoles = collect($fieldValue)->pluck('role_id')->all();
+ $user->roles()->sync($newRoles);
+ } else {
+ $user->$fieldName = $fieldValue;
+ $user->save();
+ }
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated property '$fieldName' for user {$user->user_name}.", [
+ 'type' => 'account_update_field',
+ 'user_id' => $currentUser->id
+ ]);
+ });
+
+ // Add success messages
+ if ($fieldName == 'flag_enabled') {
+ if ($fieldValue == '1') {
+ $ms->addMessageTranslated('success', 'ENABLE_SUCCESSFUL', [
+ 'user_name' => $user->user_name
+ ]);
+ } else {
+ $ms->addMessageTranslated('success', 'DISABLE_SUCCESSFUL', [
+ 'user_name' => $user->user_name
+ ]);
+ }
+ } elseif ($fieldName == 'flag_verified') {
+ $ms->addMessageTranslated('success', 'MANUALLY_ACTIVATED', [
+ 'user_name' => $user->user_name
+ ]);
+ } else {
+ $ms->addMessageTranslated('success', 'DETAILS_UPDATED', [
+ 'user_name' => $user->user_name
+ ]);
+ }
+
+ return $response->withStatus(200);
+ }
+
+ protected function getUserFromParams($params) {
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/user/get-by-username.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and throw exception on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ // TODO: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException
+ $e = new BadRequestException();
+ foreach ($validator->errors() as $idx => $field) {
+ foreach ($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ // Get the user to delete
+ $user = $classMapper->staticMethod('user', 'where', 'user_name', $data['user_name'])
+ ->first();
+
+ return $user;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php b/main/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php
new file mode 100755
index 0000000..061d90c
--- /dev/null
+++ b/main/app/sprinkles/admin/src/ServicesProvider/ServicesProvider.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\ServicesProvider;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use UserFrosting\Sprinkle\Account\Authenticate\Authenticator;
+use UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+
+/**
+ * Registers services for the admin sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ServicesProvider
+{
+ /**
+ * Register UserFrosting's admin services.
+ *
+ * @param Container $container A DI container implementing ArrayAccess and container-interop.
+ */
+ public function register($container)
+ {
+ /**
+ * Extend the 'classMapper' service to register sprunje classes.
+ *
+ * Mappings added: 'activity_sprunje', 'group_sprunje', 'permission_sprunje', 'role_sprunje', 'user_sprunje'
+ */
+ $container->extend('classMapper', function ($classMapper, $c) {
+ $classMapper->setClassMapping('activity_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\ActivitySprunje');
+ $classMapper->setClassMapping('group_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\GroupSprunje');
+ $classMapper->setClassMapping('permission_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\PermissionSprunje');
+ $classMapper->setClassMapping('permission_user_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\PermissionUserSprunje');
+ $classMapper->setClassMapping('role_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\RoleSprunje');
+ $classMapper->setClassMapping('user_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\UserSprunje');
+ $classMapper->setClassMapping('user_permission_sprunje', 'UserFrosting\Sprinkle\Admin\Sprunje\UserPermissionSprunje');
+ return $classMapper;
+ });
+
+ /**
+ * Returns a callback that handles setting the `UF-Redirect` header after a successful login.
+ *
+ * Overrides the service definition in the account Sprinkle.
+ */
+ $container['redirect.onLogin'] = function ($c) {
+ /**
+ * This method is invoked when a user completes the login process.
+ *
+ * Returns a callback that handles setting the `UF-Redirect` header after a successful login.
+ * @param \Psr\Http\Message\ServerRequestInterface $request
+ * @param \Psr\Http\Message\ResponseInterface $response
+ * @param array $args
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ return function (Request $request, Response $response, array $args) use ($c) {
+ // Backwards compatibility for the deprecated determineRedirectOnLogin service
+ if ($c->has('determineRedirectOnLogin')) {
+ $determineRedirectOnLogin = $c->determineRedirectOnLogin;
+
+ return $determineRedirectOnLogin($response)->withStatus(200);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $c->authorizer;
+
+ $currentUser = $c->authenticator->user();
+
+ if ($authorizer->checkAccess($currentUser, 'uri_dashboard')) {
+ return $response->withHeader('UF-Redirect', $c->router->pathFor('dashboard'));
+ } elseif ($authorizer->checkAccess($currentUser, 'uri_account_settings')) {
+ return $response->withHeader('UF-Redirect', $c->router->pathFor('settings'));
+ } else {
+ return $response->withHeader('UF-Redirect', $c->router->pathFor('index'));
+ }
+ };
+ };
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php b/main/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php
new file mode 100755
index 0000000..da4f0e3
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/ActivitySprunje.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Sprunje\Sprunje;
+
+/**
+ * ActivitySprunje
+ *
+ * Implements Sprunje for the activities API.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ActivitySprunje extends Sprunje
+{
+ protected $sortable = [
+ 'occurred_at',
+ 'user',
+ 'description'
+ ];
+
+ protected $filterable = [
+ 'occurred_at',
+ 'user',
+ 'description'
+ ];
+
+ protected $name = 'activities';
+
+ /**
+ * Set the initial query used by your Sprunje.
+ */
+ protected function baseQuery()
+ {
+ $query = $this->classMapper->createInstance('activity');
+
+ return $query->joinUser();
+ }
+
+ /**
+ * Filter LIKE the user info.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterUser($query, $value)
+ {
+ // Split value on separator for OR queries
+ $values = explode($this->orSeparator, $value);
+ $query->where(function ($query) use ($values) {
+ foreach ($values as $value) {
+ $query->orLike('users.first_name', $value)
+ ->orLike('users.last_name', $value)
+ ->orLike('users.email', $value);
+ }
+ });
+ return $this;
+ }
+
+ /**
+ * Sort based on user last name.
+ *
+ * @param Builder $query
+ * @param string $direction
+ * @return $this
+ */
+ protected function sortUser($query, $direction)
+ {
+ $query->orderBy('users.last_name', $direction);
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/GroupSprunje.php b/main/app/sprinkles/admin/src/Sprunje/GroupSprunje.php
new file mode 100755
index 0000000..7c75691
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/GroupSprunje.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Sprunje\Sprunje;
+
+/**
+ * GroupSprunje
+ *
+ * Implements Sprunje for the groups API.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class GroupSprunje extends Sprunje
+{
+ protected $name = 'groups';
+
+ protected $sortable = [
+ 'name',
+ 'description'
+ ];
+
+ protected $filterable = [
+ 'name',
+ 'description'
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function baseQuery()
+ {
+ return $this->classMapper->createInstance('group')->newQuery();
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php b/main/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php
new file mode 100755
index 0000000..c1803f1
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/PermissionSprunje.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Sprunje\Sprunje;
+
+/**
+ * PermissionSprunje
+ *
+ * Implements Sprunje for the permissions API.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PermissionSprunje extends Sprunje
+{
+ protected $name = 'permissions';
+
+ protected $sortable = [
+ 'name',
+ 'properties'
+ ];
+
+ protected $filterable = [
+ 'name',
+ 'properties',
+ 'info'
+ ];
+
+ protected $excludeForAll = [
+ 'info'
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function baseQuery()
+ {
+ return $this->classMapper->createInstance('permission')->newQuery();
+ }
+
+ /**
+ * Filter LIKE the slug, conditions, or description.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterInfo($query, $value)
+ {
+ return $this->filterProperties($query, $value);
+ }
+
+ /**
+ * Filter LIKE the slug, conditions, or description.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterProperties($query, $value)
+ {
+ // Split value on separator for OR queries
+ $values = explode($this->orSeparator, $value);
+ $query->where(function ($query) use ($values) {
+ foreach ($values as $value) {
+ $query->orLike('slug', $value)
+ ->orLike('conditions', $value)
+ ->orLike('description', $value);
+ }
+ });
+ return $this;
+ }
+
+ /**
+ * Sort based on slug.
+ *
+ * @param Builder $query
+ * @param string $direction
+ * @return $this
+ */
+ protected function sortProperties($query, $direction)
+ {
+ $query->orderBy('slug', $direction);
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php b/main/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php
new file mode 100755
index 0000000..242681d
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/PermissionUserSprunje.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\NotFoundException;
+
+/**
+ * PermissionUserSprunje
+ *
+ * Implements Sprunje for retrieving a list of users for a specified permission.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PermissionUserSprunje extends UserSprunje
+{
+ protected $name = 'permission_users';
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function baseQuery()
+ {
+ // Requires a permission id
+ if (!isset($this->options['permission_id'])) {
+ throw new BadRequestException();
+ }
+
+ $permission = $this->classMapper->staticMethod('permission', 'find', $this->options['permission_id']);
+
+ // If the permission doesn't exist, return 404
+ if (!$permission) {
+ throw new NotFoundException;
+ }
+
+ // Get permission users
+ $query = $permission->users()->withVia('roles_via');
+
+ return $query;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/RoleSprunje.php b/main/app/sprinkles/admin/src/Sprunje/RoleSprunje.php
new file mode 100755
index 0000000..c5e0f8b
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/RoleSprunje.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Sprunje\Sprunje;
+
+/**
+ * RoleSprunje
+ *
+ * Implements Sprunje for the roles API.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class RoleSprunje extends Sprunje
+{
+ protected $name = 'roles';
+
+ protected $sortable = [
+ 'name',
+ 'description'
+ ];
+
+ protected $filterable = [
+ 'name',
+ 'description',
+ 'info'
+ ];
+
+ protected $excludeForAll = [
+ 'info'
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function baseQuery()
+ {
+ return $this->classMapper->createInstance('role')->newQuery();
+ }
+
+ /**
+ * Filter LIKE name OR description.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterInfo($query, $value)
+ {
+ // Split value on separator for OR queries
+ $values = explode($this->orSeparator, $value);
+ $query->where(function ($query) use ($values) {
+ foreach ($values as $value) {
+ $query->orLike('name', $value)
+ ->orLike('description', $value);
+ }
+ });
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php b/main/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php
new file mode 100755
index 0000000..6142e74
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/UserPermissionSprunje.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\NotFoundException;
+
+/**
+ * UserPermissionSprunje
+ *
+ * Implements Sprunje for retrieving a list of permissions for a specified user.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class UserPermissionSprunje extends PermissionSprunje
+{
+ protected $name = 'user_permissions';
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function baseQuery()
+ {
+ // Requires a user id
+ if (!isset($this->options['user_id'])) {
+ throw new BadRequestException();
+ }
+
+ $user = $this->classMapper->staticMethod('user', 'find', $this->options['user_id']);
+
+ // If the user doesn't exist, return 404
+ if (!$user) {
+ throw new NotFoundException;
+ }
+
+ // Get user permissions
+ $query = $user->permissions()->withVia('roles_via');
+
+ return $query;
+ }
+}
diff --git a/main/app/sprinkles/admin/src/Sprunje/UserSprunje.php b/main/app/sprinkles/admin/src/Sprunje/UserSprunje.php
new file mode 100755
index 0000000..12378f9
--- /dev/null
+++ b/main/app/sprinkles/admin/src/Sprunje/UserSprunje.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+namespace UserFrosting\Sprinkle\Admin\Sprunje;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Facades\Translator;
+use UserFrosting\Sprinkle\Core\Sprunje\Sprunje;
+
+/**
+ * UserSprunje
+ *
+ * Implements Sprunje for the users API.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class UserSprunje extends Sprunje
+{
+ protected $name = 'users';
+
+ protected $listable = [
+ 'status'
+ ];
+
+ protected $sortable = [
+ 'name',
+ 'last_activity',
+ 'status'
+ ];
+
+ protected $filterable = [
+ 'name',
+ 'last_activity',
+ 'status'
+ ];
+
+ protected $excludeForAll = [
+ 'last_activity'
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function baseQuery()
+ {
+ $query = $this->classMapper->createInstance('user');
+
+ // Join user's most recent activity
+ return $query->joinLastActivity()->with('lastActivity');
+ }
+
+ /**
+ * Filter LIKE the last activity description.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterLastActivity($query, $value)
+ {
+ // Split value on separator for OR queries
+ $values = explode($this->orSeparator, $value);
+ $query->where(function ($query) use ($values) {
+ foreach ($values as $value) {
+ $query->orLike('activities.description', $value);
+ }
+ });
+ return $this;
+ }
+
+ /**
+ * Filter LIKE the first name, last name, or email.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterName($query, $value)
+ {
+ // Split value on separator for OR queries
+ $values = explode($this->orSeparator, $value);
+ $query->where(function ($query) use ($values) {
+ foreach ($values as $value) {
+ $query->orLike('first_name', $value)
+ ->orLike('last_name', $value)
+ ->orLike('email', $value);
+ }
+ });
+ return $this;
+ }
+
+ /**
+ * Filter by status (active, disabled, unactivated)
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterStatus($query, $value)
+ {
+ // Split value on separator for OR queries
+ $values = explode($this->orSeparator, $value);
+ $query->where(function ($query) use ($values) {
+ foreach ($values as $value) {
+ if ($value == 'disabled') {
+ $query->orWhere('flag_enabled', 0);
+ } elseif ($value == 'unactivated') {
+ $query->orWhere('flag_verified', 0);
+ } elseif ($value == 'active') {
+ $query->orWhere(function ($query) {
+ $query->where('flag_enabled', 1)->where('flag_verified', 1);
+ });
+ }
+ }
+ });
+ return $this;
+ }
+
+ /**
+ * Return a list of possible user statuses.
+ *
+ * @return array
+ */
+ protected function listStatus()
+ {
+ return [
+ [
+ 'value' => 'active',
+ 'text' => Translator::translate('ACTIVE')
+ ],
+ [
+ 'value' => 'unactivated',
+ 'text' => Translator::translate('UNACTIVATED')
+ ],
+ [
+ 'value' => 'disabled',
+ 'text' => Translator::translate('DISABLED')
+ ]
+ ];
+ }
+
+ /**
+ * Sort based on last activity time.
+ *
+ * @param Builder $query
+ * @param string $direction
+ * @return $this
+ */
+ protected function sortLastActivity($query, $direction)
+ {
+ $query->orderBy('activities.occurred_at', $direction);
+ return $this;
+ }
+
+ /**
+ * Sort based on last name.
+ *
+ * @param Builder $query
+ * @param string $direction
+ * @return $this
+ */
+ protected function sortName($query, $direction)
+ {
+ $query->orderBy('last_name', $direction);
+ return $this;
+ }
+
+ /**
+ * Sort active, unactivated, disabled
+ *
+ * @param Builder $query
+ * @param string $direction
+ * @return $this
+ */
+ protected function sortStatus($query, $direction)
+ {
+ $query->orderBy('flag_enabled', $direction)->orderBy('flag_verified', $direction);
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/admin/templates/forms/group.html.twig b/main/app/sprinkles/admin/templates/forms/group.html.twig
new file mode 100755
index 0000000..36d6632
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/forms/group.html.twig
@@ -0,0 +1,69 @@
+<form class="js-form" method="{{form.method | default('POST')}}" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <div class="row">
+ {% block group_form %}
+ {% if 'name' not in form.fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label>{{translate("GROUP.NAME")}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-edit fa-fw"></i></span>
+ <input type="text" class="form-control" name="name" autocomplete="off" value="{{group.name}}" placeholder="{{translate("GROUP.NAME_EXPLAIN")}}" {% if 'name' in form.fields.disabled %}disabled{% endif %}>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'slug' not in form.fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label>{{translate("SLUG")}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-tag fa-fw"></i></span>
+ <input type="text" class="form-control" name="slug" autocomplete="off" value="{{group.slug}}" placeholder="{{translate("SLUG")}}" {% if 'slug' in form.fields.disabled %}disabled{% endif %} readonly>
+ {% if 'slug' not in form.fields.disabled %}
+ <span class="input-group-btn" data-toggle="buttons">
+ <label class="btn btn-primary">
+ <input type="checkbox" id="form-group-slug-override" autocomplete="off"> {{translate("OVERRIDE")}}
+ </label>
+ </span>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'icon' not in fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label>{{translate("GROUP.ICON")}}</label>
+ <div class="input-group">
+ <span class="input-group-addon icon-preview"><i class="{{group.icon}} fa-fw"></i></span>
+ <input type="text" class="form-control" name="icon" autocomplete="off" value="{{group.icon}}" placeholder="{{translate("GROUP.ICON_EXPLAIN")}}" {% if 'icon' in form.fields.disabled %}disabled{% endif %}>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'description' not in fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label for="input_description">{{translate("DESCRIPTION")}}</label>
+ <textarea id="input_description" class="form-control" type="text" name="description" {% if 'description' in form.fields.disabled %}disabled{% endif %} rows=6>{{group.description}}</textarea>
+ </div>
+ </div>
+ {% endif %}
+ {% endblock %}
+ </div><br>
+ <div class="row">
+ <div class="col-xs-6 col-sm-4">
+ <button type="submit" class="btn btn-block btn-lg btn-success">{{form.submit_text}}</button>
+ </div>
+ <div class="col-xs-4 col-sm-3 pull-right">
+ <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate("CANCEL")}}</button>
+ </div>
+ </div>
+</form>
+<!-- Include validation rules -->
+<script>
+{% include "pages/partials/page.js.twig" %}
+</script>
diff --git a/main/app/sprinkles/admin/templates/forms/role.html.twig b/main/app/sprinkles/admin/templates/forms/role.html.twig
new file mode 100755
index 0000000..46a4477
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/forms/role.html.twig
@@ -0,0 +1,56 @@
+<form class="js-form" method="{{form.method | default('POST')}}" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <div class="row">
+ {% if 'name' not in form.fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label>{{translate("ROLE.NAME")}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-edit fa-fw"></i></span>
+ <input type="text" class="form-control" name="name" autocomplete="off" value="{{role.name}}" placeholder="{{translate("ROLE.NAME_EXPLAIN")}}" {% if 'name' in form.fields.disabled %}disabled{% endif %}>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'slug' not in form.fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label>{{translate("SLUG")}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-tag fa-fw"></i></span>
+ <input type="text" class="form-control" name="slug" autocomplete="off" value="{{role.slug}}" placeholder="{{translate("SLUG")}}" {% if 'slug' in form.fields.disabled %}disabled{% endif %} readonly>
+ {% if 'slug' not in form.fields.disabled %}
+ <span class="input-group-btn" data-toggle="buttons">
+ <label class="btn btn-primary">
+ <input type="checkbox" id="form-role-slug-override" autocomplete="off"> {{translate("OVERRIDE")}}
+ </label>
+ </span>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'description' not in fields.hidden %}
+ <div class="col-sm-12">
+ <div class="form-group">
+ <label for="input_description">{{translate("DESCRIPTION")}}</label>
+ <textarea id="input_description" class="form-control" type="text" name="description" {% if 'description' in form.fields.disabled %}disabled{% endif %} rows=6>{{role.description}}</textarea>
+ </div>
+ </div>
+ {% endif %}
+ </div><br>
+ <div class="row">
+ <div class="col-xs-8 col-sm-4">
+ <button type="submit" class="btn btn-block btn-lg btn-success">{{form.submit_text}}</button>
+ </div>
+ <div class="col-xs-4 col-sm-4 pull-right">
+ <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate("CANCEL")}}</button>
+ </div>
+ </div>
+</form>
+<!-- Include validation rules -->
+<script>
+{% include "pages/partials/page.js.twig" %}
+</script>
diff --git a/main/app/sprinkles/admin/templates/forms/user.html.twig b/main/app/sprinkles/admin/templates/forms/user.html.twig
new file mode 100755
index 0000000..3ee7fc9
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/forms/user.html.twig
@@ -0,0 +1,125 @@
+<form class="js-form" method="{{form.method | default('POST')}}" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <div class="row">
+ {% block user_form %}
+ {% if 'user_name' not in form.fields.hidden %}
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label>{{translate('USERNAME')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-edit fa-fw"></i></span>
+ <input type="text" class="form-control" name="user_name" autocomplete="off" value="{{user.user_name}}" placeholder="{{translate('USERNAME')}}" {% if 'user_name' in form.fields.disabled %}disabled{% endif %}>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'group' not in form.fields.hidden %}
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="input-group">{{translate('GROUP')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-users fa-fw"></i></span>
+ {% if 'group' in form.fields.disabled %}
+ <input type="text" class="form-control" name="theme" value="{{user.group.name}}" disabled>
+ {% else %}
+ <select id="input-group" class="form-control js-select2" name="group_id">
+ {% for group in groups %}
+ <option value="{{group.id}}" {% if (group.id == user.group_id) %}selected{% endif %}>{{group.name}}</option>
+ {% endfor %}
+ </select>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'name' not in form.fields.hidden %}
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label>{{translate('FIRST_NAME')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-edit fa-fw"></i></span>
+ <input type="text" class="form-control" name="first_name" autocomplete="off" value="{{user.first_name}}" placeholder="{{translate('FIRST_NAME')}}" {% if 'name' in form.fields.disabled %}disabled{% endif %}>
+ </div>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label>{{translate('LAST_NAME')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-edit fa-fw"></i></span>
+ <input type="text" class="form-control" name="last_name" autocomplete="off" value="{{user.last_name}}" placeholder="{{translate('LAST_NAME')}}" {% if 'name' in form.fields.disabled %}disabled{% endif %}>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'email' not in form.fields.hidden %}
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label>{{translate('EMAIL')}}</label>
+ <div class="input-group js-copy-container">
+ <span class="input-group-addon"><i class="fa fa-envelope fa-fw"></i></span>
+ <input type="text" class="form-control js-copy-target" name="email" autocomplete="off" value="{{user.email}}" placeholder="{{translate('EMAIL')}}" {% if 'email' in form.fields.disabled %}disabled{% endif %}>
+ {% if 'email' in form.fields.disabled %}
+ <span class="input-group-btn">
+ <button class="btn btn-default uf-copy-trigger js-copy-trigger" type="button"><i class="fa fa-clipboard"></i></button>
+ </span>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'theme' not in form.fields.hidden %}
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="input-theme">{{translate('THEME')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-puzzle-piece fa-fw"></i></span>
+ {% if 'theme' in form.fields.disabled %}
+ <input type="text" class="form-control" name="theme" value="{{themes[user.theme]}}" disabled>
+ {% else %}
+ <select id="input-theme" class="form-control js-select2" name="theme">
+ {% for option, label in theme %}
+ <option value="{{option}}" {% if (option == user.theme) %}selected{% endif %}>{{label}}</option>
+ {% endfor %}
+ </select>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if 'locale' not in form.fields.hidden %}
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="input-locale">{{translate('LOCALE')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-language fa-fw"></i></span>
+ {% if 'locale' in form.fields.disabled %}
+ <input type="text" class="form-control" name="theme" value="{{locales[user.locale]}}" disabled>
+ {% else %}
+ <select id="input-locale" class="form-control js-select2" name="locale">
+ {% for option, label in locales %}
+ <option value="{{option}}" {% if (option == user.locale) %}selected{% endif %}>{{label}}</option>
+ {% endfor %}
+ </select>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% endblock %}
+ </div><br>
+ <div class="row">
+ <div class="col-xs-8 col-sm-4">
+ <button type="submit" class="btn btn-block btn-lg btn-success">{{form.submit_text}}</button>
+ </div>
+ <div class="col-xs-4 col-sm-3 pull-right">
+ <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate('CANCEL')}}</button>
+ </div>
+ </div>
+</form>
+<!-- Include validation rules -->
+<script>
+{% include "pages/partials/page.js.twig" %}
+</script>
diff --git a/main/app/sprinkles/admin/templates/mail/password-create.html.twig b/main/app/sprinkles/admin/templates/mail/password-create.html.twig
new file mode 100755
index 0000000..854eb77
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/mail/password-create.html.twig
@@ -0,0 +1,19 @@
+{% block subject %}
+ {{site.title}} - please set a password for your new account
+{% endblock %}
+
+{% block body %}
+<p>
+ Dear {{user.first_name}},
+</p>
+<p>
+ Someone has created an account for you with {{site.title}} ({{site.uri.public}}). Your username is <b>{{user.user_name}}</b>.
+</p>
+<p>
+ To access your account, you must first create a password by visiting: <a href="{{site.uri.public}}/account/set-password/confirm?token={{token}}">{{site.uri.public}}/account/set-password/confirm?token={{token}}</a>. This link has been generated especially for you, and will expire in {{create_password_expiration}}. Do not share it with anyone!
+</p>
+<p>
+ With regards,<br>
+ The {{site.title}} Team
+</p>
+{% endblock %} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/modals/confirm-clear-cache.html.twig b/main/app/sprinkles/admin/templates/modals/confirm-clear-cache.html.twig
new file mode 100755
index 0000000..e5457d3
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/confirm-clear-cache.html.twig
@@ -0,0 +1,17 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("CACHE.CLEAR")}}{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="post" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <h4>{{translate("CACHE.CLEAR_CONFIRM")}}<br><small>{{translate("DELETE_CANNOT_UNDONE")}}</small></h4>
+ <br>
+ <div class="btn-group-action">
+ <button type="submit" class="btn btn-danger btn-lg btn-block">{{translate("CACHE.CLEAR_CONFIRM_YES")}}</button>
+ <button type="button" class="btn btn-default btn-lg btn-block" data-dismiss="modal">{{translate("CANCEL")}}</button>
+ </div>
+</form>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/confirm-delete-group.html.twig b/main/app/sprinkles/admin/templates/modals/confirm-delete-group.html.twig
new file mode 100755
index 0000000..7889a1e
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/confirm-delete-group.html.twig
@@ -0,0 +1,17 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("GROUP.DELETE")}}{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="delete" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <h4>{{translate("GROUP.DELETE_CONFIRM", {name: group.name})}}<br><small>{{translate("DELETE_CANNOT_UNDONE")}}</small></h4>
+ <br>
+ <div class="btn-group-action">
+ <button type="submit" class="btn btn-danger btn-lg btn-block">{{translate("GROUP.DELETE_YES")}}</button>
+ <button type="button" class="btn btn-default btn-lg btn-block" data-dismiss="modal">{{translate("CANCEL")}}</button>
+ </div>
+</form>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/confirm-delete-role.html.twig b/main/app/sprinkles/admin/templates/modals/confirm-delete-role.html.twig
new file mode 100755
index 0000000..618039b
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/confirm-delete-role.html.twig
@@ -0,0 +1,17 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("ROLE.DELETE")}}{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="delete" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <h4>{{translate("ROLE.DELETE_CONFIRM", {name: role.name})}}<br><small>{{translate("DELETE_CANNOT_UNDONE")}}</small></h4>
+ <br>
+ <div class="btn-group-action">
+ <button type="submit" class="btn btn-danger btn-lg btn-block">{{translate("ROLE.DELETE_YES")}}</button>
+ <button type="button" class="btn btn-default btn-lg btn-block" data-dismiss="modal">{{translate("CANCEL")}}</button>
+ </div>
+</form>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/confirm-delete-user.html.twig b/main/app/sprinkles/admin/templates/modals/confirm-delete-user.html.twig
new file mode 100755
index 0000000..ce86301
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/confirm-delete-user.html.twig
@@ -0,0 +1,17 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("USER.DELETE")}}{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="delete" action="{{site.uri.public}}/{{form.action}}">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <h4>{{translate("USER.DELETE_CONFIRM", {name: user.user_name})}}<br><small>{{translate("DELETE_CANNOT_UNDONE")}}</small></h4>
+ <br>
+ <div class="btn-group-action">
+ <button type="submit" class="btn btn-danger btn-lg btn-block">{{translate("USER.DELETE_YES")}}</button>
+ <button type="button" class="btn btn-default btn-lg btn-block" data-dismiss="modal">{{translate("CANCEL")}}</button>
+ </div>
+</form>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/group.html.twig b/main/app/sprinkles/admin/templates/modals/group.html.twig
new file mode 100755
index 0000000..be2d98c
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/group.html.twig
@@ -0,0 +1,7 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate('GROUP')}}{% endblock %}
+
+{% block modal_body %}
+ {% include "forms/group.html.twig" %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/role-manage-permissions.html.twig b/main/app/sprinkles/admin/templates/modals/role-manage-permissions.html.twig
new file mode 100755
index 0000000..3914d2e
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/role-manage-permissions.html.twig
@@ -0,0 +1,94 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("PERMISSION.MANAGE")}}{% endblock %}
+
+{% block modal_size %}modal-lg{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="PUT" action="{{site.uri.public}}/api/roles/r/{{role.slug}}/permissions">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <div class="js-form-permissions">
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>{{translate("NAME")}}</th>
+ <th>{{translate("DESCRIPTION")}}</th>
+ <th>{{translate("PERMISSION.HOOK_CONDITION")}}</th>
+ <th>{{translate("REMOVE")}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ <div class="padding-bottom">
+ <label>{{translate("PERMISSION.ASSIGN_NEW")}}:</label>
+ <select class="form-control js-select-new" type="text">
+ <option></option>
+ </select>
+ </div>
+ </div>
+ <br>
+ <div class="row">
+ <div class="col-xs-12 col-sm-6">
+ <button type="submit" class="btn btn-block btn-lg btn-success">{{translate("PERMISSION.UPDATE")}}</button>
+ </div>
+ <div class="col-xs-12 col-sm-3 pull-right">
+ <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate('CANCEL')}}</button>
+ </div>
+ </div>
+</form>
+
+{# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+#}
+{% verbatim %}
+<script id="role-permissions-select-option" type="text/x-handlebars-template">
+ <div>
+ <strong>
+ {{name}}
+ </strong>
+ <br>
+ {{description}}
+ <div>
+ <code>{{this.slug}}</code>
+ </div>
+ <div>
+ ↳ <code>{{conditions}}</code>
+ </div>
+ </div>
+</script>
+
+<script id="role-permissions-row" type="text/x-handlebars-template">
+ <tr class="uf-collection-row">
+ <td>
+ {{name}}
+ <input type="hidden" name="value[{{ rownum }}][permission_id]" value="{{id}}">
+ </td>
+ <td>
+ {{description}}
+ </td>
+ <td class="uf-collection-col-wrap">
+ <div>
+ <code>{{this.slug}}</code>
+ </div>
+ <div>
+ ↳ <code>{{conditions}}</code>
+ </div>
+ </td>
+ <td>
+ <button type="button" class="btn btn-link btn-trash js-delete-row pull-right" title="Delete"> <i class="fa fa-trash"></i> </button>
+ </td>
+ </tr>
+</script>
+{% endverbatim %}
+
+<!-- Include validation rules -->
+<script>
+ {% include "pages/partials/page.js.twig" %}
+</script>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/role.html.twig b/main/app/sprinkles/admin/templates/modals/role.html.twig
new file mode 100755
index 0000000..6346461
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/role.html.twig
@@ -0,0 +1,7 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate('ROLE')}}{% endblock %}
+
+{% block modal_body %}
+ {% include "forms/role.html.twig" %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/user-manage-roles.html.twig b/main/app/sprinkles/admin/templates/modals/user-manage-roles.html.twig
new file mode 100755
index 0000000..b41c60b
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/user-manage-roles.html.twig
@@ -0,0 +1,77 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("ROLE.MANAGE")}}{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="PUT" action="{{site.uri.public}}/api/users/u/{{user.user_name}}/roles">
+ {% include "forms/csrf.html.twig" %}
+ <div class="js-form-alerts">
+ </div>
+ <div class="js-form-roles">
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>{{translate("NAME")}}</th>
+ <th>{{translate("DESCRIPTION")}}</th>
+ <th>{{translate("REMOVE")}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ <div class="padding-bottom">
+ <label>{{translate("ROLE.ASSIGN_NEW")}}:</label>
+ <select class="form-control js-select-new" type="text">
+ <option></option>
+ </select>
+ </div>
+ </div>
+ <br>
+ <div class="row">
+ <div class="col-xs-8 col-sm-4">
+ <button type="submit" class="btn btn-block btn-lg btn-success">{{translate("UPDATE")}}</button>
+ </div>
+ <div class="col-xs-4 col-sm-3 pull-right">
+ <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate('CANCEL')}}</button>
+ </div>
+ </div>
+</form>
+
+{# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+#}
+{% verbatim %}
+<script id="user-roles-select-option" type="text/x-handlebars-template">
+ <div>
+ <strong>
+ {{name}}
+ </strong>
+ <br>
+ {{description}}
+ </div>
+</script>
+
+<script id="user-roles-row" type="text/x-handlebars-template">
+ <tr class="uf-collection-row">
+ <td>
+ {{name}}
+ <input type="hidden" name="value[{{ rownum }}][role_id]" value="{{id}}">
+ </td>
+ <td>
+ {{description}}
+ </td>
+ <td>
+ <button type="button" class="btn btn-link btn-trash js-delete-row pull-right" title="Delete"> <i class="fa fa-trash"></i> </button>
+ </td>
+ </tr>
+</script>
+{% endverbatim %}
+
+<!-- Include validation rules -->
+<script>
+ {% include "pages/partials/page.js.twig" %}
+</script>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/user-set-password.html.twig b/main/app/sprinkles/admin/templates/modals/user-set-password.html.twig
new file mode 100755
index 0000000..922d4e2
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/user-set-password.html.twig
@@ -0,0 +1,62 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate("USER.ADMIN.CHANGE_PASSWORD")}}{% endblock %}
+
+{% block modal_body %}
+<form class="js-form" method="PUT" action="{{site.uri.public}}/api/users/u/{{user.user_name}}">
+ {% include "forms/csrf.html.twig" %}
+ <!-- Prevent browsers from trying to autofill the password field. See http://stackoverflow.com/a/23234498/2970321 -->
+ <input type="text" style="display:none">
+ <input type="password" style="display:none">
+ <div class="js-form-alerts">
+ </div>
+ <div class="row">
+ <div class="col-sm-12">
+ <div class="radio">
+ <label for="change_password_mode_link">
+ <input type="radio" name="change_password_mode" id="change_password_mode_link" value="link" checked>
+ {{translate("USER.ADMIN.SEND_PASSWORD_LINK")}}
+ </label>
+ </div>
+ </div>
+ <div class="col-sm-12">
+ <div class="radio">
+ <label for="change_password_mode_manual">
+ <input type="radio" name="change_password_mode" id="change_password_mode_manual" value="manual">
+ {{translate("USER.ADMIN.SET_PASSWORD")}}:
+ </label>
+ </div>
+ <div class="row controls-password">
+ <div class="col-sm-11 col-sm-offset-1">
+ <div class="form-group">
+ <label>{{translate('PASSWORD')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-key"></i></span>
+ <input type="password" class="form-control" name="value" autocomplete="off" value="" placeholder="{{translate("PASSWORD.BETWEEN", {min: 12, max: 50})}}">
+ </div>
+ </div>
+ <div class="form-group">
+ <label>{{translate('PASSWORD.CONFIRM')}}</label>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="fa fa-key"></i></span>
+ <input type="password" class="form-control" name="passwordc" autocomplete="off" value="" placeholder="{{translate('PASSWORD.CONFIRM')}}">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div><br>
+ <div class="row">
+ <div class="col-xs-8 col-sm-4">
+ <button type="submit" class="btn btn-block btn-lg btn-success">{{translate('SUBMIT')}}</button>
+ </div>
+ <div class="col-xs-4 col-sm-3 pull-right">
+ <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate('CANCEL')}}</button>
+ </div>
+ </div>
+</form>
+<!-- Include validation rules -->
+<script>
+ {% include "pages/partials/page.js.twig" %}
+</script>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/modals/user.html.twig b/main/app/sprinkles/admin/templates/modals/user.html.twig
new file mode 100755
index 0000000..892fe4f
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/modals/user.html.twig
@@ -0,0 +1,7 @@
+{% extends "modals/modal.html.twig" %}
+
+{% block modal_title %}{{translate('USER')}}{% endblock %}
+
+{% block modal_body %}
+ {% include "forms/user.html.twig" %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/navigation/navbar.html.twig b/main/app/sprinkles/admin/templates/navigation/navbar.html.twig
new file mode 100755
index 0000000..b2cf699
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/navigation/navbar.html.twig
@@ -0,0 +1,15 @@
+{% block dashboard_navbar %}
+ <div class="navbar-custom-menu">
+ <ul class="nav navbar-nav">
+ {% if current_user.isMaster() %}
+ <li class="hidden-xs hidden-sm"><a href="#">{{ translate("HEADER_MESSAGE_ROOT") | upper }}</a></li>
+ {% endif %}
+
+ {# additional elements (e.g., menus, messages) before the user account drop down #}
+ {% block dashboard_navbar_extra %} {% endblock %}
+
+ {# User Account: style can be found in dropdown.less #}
+ {% include "navigation/user-card.html.twig" %}
+ </ul>
+ </div>
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/navigation/sidebar-menu.html.twig b/main/app/sprinkles/admin/templates/navigation/sidebar-menu.html.twig
new file mode 100755
index 0000000..bde2674
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/navigation/sidebar-menu.html.twig
@@ -0,0 +1,43 @@
+{% block navigation %}
+ {% if checkAccess('uri_dashboard') %}
+ <li>
+ <a href="{{site.uri.public}}/dashboard"><i class="fa fa-dashboard fa-fw"></i> <span>{{ translate("DASHBOARD") }}</span></a>
+ </li>
+ {% endif %}
+ {% if checkAccess('uri_users') %}
+ <li>
+ <a href="{{site.uri.public}}/users"><i class="fa fa-user fa-fw"></i> <span>{{ translate("USER", 2) }}</span></a>
+ </li>
+ {% elseif checkAccess('uri_group', {
+ 'group': current_user.group
+ }) %}
+ <li>
+ <a href="{{site.uri.public}}/groups/g/{{current_user.group.slug}}"><i class="{{current_user.group.icon}} fa-fw"></i> <span>{{ translate("GROUP.MANAGE") }}</span></a>
+ </li>
+ {% endif %}
+ {% if checkAccess('uri_activities') %}
+ <li>
+ <a href="{{site.uri.public}}/activities"><i class="fa fa-tasks fa-fw"></i> <span>{{ translate("ACTIVITY", 2) }}</span></a>
+ </li>
+ {% endif %}
+ {% if checkAccess('uri_roles') %}
+ <li>
+ <a href="{{site.uri.public}}/roles"><i class="fa fa-drivers-license fa-fw"></i> <span>{{ translate("ROLE", 2) }}</span></a>
+ </li>
+ {% endif %}
+ {% if checkAccess('uri_permissions') %}
+ <li>
+ <a href="{{site.uri.public}}/permissions"><i class="fa fa-key fa-fw"></i> <span>{{ translate("PERMISSION", 2) }}</span></a>
+ </li>
+ {% endif %}
+ {% if checkAccess('uri_groups') %}
+ <li>
+ <a href="{{site.uri.public}}/groups"><i class="fa fa-users fa-fw"></i> <span>{{ translate("GROUP", 2) }}</span></a>
+ </li>
+ {% endif %}
+ {% if checkAccess('update_site_config') %}
+ <li>
+ <a href="{{site.uri.public}}/settings"><i class="fa fa-gears fa-fw"></i> <span>{{ translate("SITE.CONFIG.MANAGER") }}</span></a>
+ </li>
+ {% endif %}
+{% endblock %} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/navigation/sidebar-user.html.twig b/main/app/sprinkles/admin/templates/navigation/sidebar-user.html.twig
new file mode 100755
index 0000000..018e644
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/navigation/sidebar-user.html.twig
@@ -0,0 +1,10 @@
+<!-- Sidebar user panel -->
+<div class="user-panel">
+ <div class="pull-left image">
+ <img src="{{ current_user.avatar }}" class="img-circle" alt="User Image">
+ </div>
+ <div class="pull-left info">
+ <p>{{current_user.first_name}} {{current_user.last_name}}</p>
+ <i class="{{current_user.group.icon}}"></i> {{current_user.group.name}}
+ </div>
+</div> \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/navigation/sidebar.html.twig b/main/app/sprinkles/admin/templates/navigation/sidebar.html.twig
new file mode 100755
index 0000000..1b2939e
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/navigation/sidebar.html.twig
@@ -0,0 +1,10 @@
+{% block sidebar_user %}
+ {% include 'navigation/sidebar-user.html.twig' %}
+{% endblock %}
+
+{% block sidebar_menu %}
+<ul class="sidebar-menu">
+ <li class="header" style="text-transform: uppercase">{{translate('NAVIGATION')}}</li>
+ {% include 'navigation/sidebar-menu.html.twig' %}
+</ul>
+{% endblock %} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/navigation/user-card.html.twig b/main/app/sprinkles/admin/templates/navigation/user-card.html.twig
new file mode 100755
index 0000000..36fdb4b
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/navigation/user-card.html.twig
@@ -0,0 +1,8 @@
+{% extends "@account/navigation/user-card.html.twig" %}
+
+{% block userCard_menu %}
+ {% if checkAccess('uri_dashboard') %}
+ <a href="{{site.uri.public}}/dashboard" class="btn btn-default btn-flat btn-block">{{translate("DASHBOARD")}}</a>
+ {% endif %}
+ {{ parent() }}
+{% endblock %} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/pages/abstract/dashboard.html.twig b/main/app/sprinkles/admin/templates/pages/abstract/dashboard.html.twig
new file mode 100755
index 0000000..2a53de4
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/abstract/dashboard.html.twig
@@ -0,0 +1,87 @@
+{% extends "pages/abstract/base.html.twig" %}
+
+{% block stylesheets_page_group %}
+ <!-- Page-group-specific CSS asset bundle -->
+ {{ assets.css('css/admin') | raw }}
+{% endblock %}
+
+{% block body_attributes %}
+ {% if current_user.isMaster() %}
+ class="hold-transition skin-red sidebar-mini"
+ {% else %}
+ class="hold-transition skin-{{site.AdminLTE.skin}} sidebar-mini"
+ {% endif %}
+{% endblock %}
+
+{% block content %}
+ {# This needs to be here (early in the body) to make sure the animation doesn't fire #}
+ <script>
+ (function () {
+ if (Boolean(sessionStorage.getItem('sidebar-toggle-collapsed'))) {
+ var body = document.getElementsByTagName('body')[0];
+ body.className = body.className + ' sidebar-collapse';
+ }
+ })();
+ </script>
+
+ <div class="wrapper">
+
+ <header class="main-header">
+ <!-- Logo -->
+ {% block navbar_logo %}
+ <a href="{{site.uri.public}}" class="logo">
+ <i class="fa fa-home"></i>
+ {{site.title}}
+ </a>
+ {% endblock %}
+ <!-- Header Navbar: style can be found in header.less -->
+ <nav class="navbar navbar-static-top">
+ <!-- Sidebar toggle button-->
+ <a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
+ <span class="sr-only">Toggle navigation</span>
+ </a>
+ <!-- Main nav buttons -->
+ {% include "navigation/navbar.html.twig" %}
+ </nav>
+ </header>
+ <!-- Left side column. contains the logo and sidebar -->
+ <aside class="main-sidebar">
+ <!-- sidebar: style can be found in sidebar.less -->
+ <section class="sidebar">
+ {% include 'navigation/sidebar.html.twig' %}
+ </section>
+ <!-- /.sidebar -->
+ </aside>
+
+ <!-- Content Wrapper. Contains page content -->
+ <div class="content-wrapper">
+ <!-- Content Header (Page header) -->
+ {% block content_header %}
+ <section class="content-header">
+ <h1>{% block header_title %}{{ block('page_title') }}{% endblock %}</h1>
+ {% if block('page_description') is not empty %}<h1><small>{% block header_description %}{{ block('page_description') }}{% endblock %}</small></h1>{% endif %}
+ {% block breadcrumb %}
+ {% include 'navigation/breadcrumb.html.twig' with {page_title: block('page_title')} %}
+ {% endblock %}
+ <div id="alerts-page"></div>
+ </section>
+ {% endblock %}
+ <section class="content">
+ {% block body_matter %}{% endblock %}
+ </section>
+ </div>
+ <!-- /.content-wrapper -->
+
+ <!-- Footer -->
+ {% block footer %}
+ {% include "pages/partials/footer.html.twig" %}
+ {% endblock %}
+
+ </div>
+ <!-- ./wrapper -->
+
+{% endblock %}
+
+{% block scripts_page_group %}
+ {{ assets.js('js/admin') | raw }}
+{% endblock %} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/pages/activities.html.twig b/main/app/sprinkles/admin/templates/pages/activities.html.twig
new file mode 100755
index 0000000..bcbd9c6
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/activities.html.twig
@@ -0,0 +1,46 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("ACTIVITY", 2) }}{% endblock %}
+
+{% block page_description %}{{ translate("ACTIVITY.PAGE") }}.{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-md-12">
+ <div id="widget-activities" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-tasks fa-fw"></i> {{translate('ACTIVITY', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/activities.html.twig" with {
+ "table" : {
+ "id" : "table-activities",
+ "columns" : ["user"]
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include validation rules -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/activities') | raw }}
+
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/dashboard.html.twig b/main/app/sprinkles/admin/templates/pages/dashboard.html.twig
new file mode 100755
index 0000000..f9c85a3
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/dashboard.html.twig
@@ -0,0 +1,282 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("DASHBOARD") }}{% endblock %}
+{% block page_description %}{% endblock %}
+
+{% block body_matter %}
+ <!-- Info boxes -->
+ {% if checkAccess('uri_users') %}
+ <div class="row">
+ <div class="col-md-4 col-sm-6 col-xs-12">
+ <a href="{{site.uri.public}}/users">
+ <div class="info-box">
+ <span class="info-box-icon bg-aqua"><i class="fa fa-user fa-fw"></i></span>
+ <div class="info-box-content">
+ <span class="info-box-text">{{ translate("USER", 2) }}</span>
+ <span class="info-box-number">{{counter.users}}</span>
+ </div>
+ <!-- /.info-box-content -->
+ </div>
+ <!-- /.info-box -->
+ </a>
+ </div>
+ <!-- /.col -->
+
+ <div class="col-md-4 col-sm-6 col-xs-12">
+ <a href="{{site.uri.public}}/roles">
+ <div class="info-box">
+ <span class="info-box-icon bg-red"><i class="fa fa-drivers-license"></i></span>
+ <div class="info-box-content">
+ <span class="info-box-text">{{ translate("ROLE", 2) }}</span>
+ <span class="info-box-number">{{counter.roles}}</span>
+ </div>
+ <!-- /.info-box-content -->
+ </div>
+ <!-- /.info-box -->
+ </a>
+ </div>
+ <!-- /.col -->
+
+ <div class="col-md-4 col-sm-6 col-xs-12">
+ <a href="{{site.uri.public}}/groups">
+ <div class="info-box">
+ <span class="info-box-icon bg-green"><i class="fa fa-users"></i></span>
+ <div class="info-box-content">
+ <span class="info-box-text">{{ translate("GROUP", 2) }}</span>
+ <span class="info-box-number">{{counter.groups}}</span>
+ </div>
+ <!-- /.info-box-content -->
+ </div>
+ <!-- /.info-box -->
+ </a>
+ </div>
+ <!-- /.col -->
+ </div>
+ <!-- /.row -->
+
+ {% elseif checkAccess('uri_group', {
+ 'group': current_user.group
+ }) %}
+ <div class="row">
+ <div class="col-sm-6 col-xs-12">
+ <div class="info-box">
+ <span class="info-box-icon bg-aqua"><i class="{{current_user.group.icon}}"></i></span>
+ <div class="info-box-content">
+ <h1>{{current_user.group.name}}</h1>
+ </div>
+ <!-- /.info-box-content -->
+ </div>
+ <!-- /.info-box -->
+ </div>
+ <!-- /.col -->
+ <div class="col-sm-6 col-xs-12">
+ <div class="info-box">
+ <span class="info-box-icon bg-aqua"><i class="fa fa-user fa-fw"></i></span>
+ <div class="info-box-content">
+ <span class="info-box-text">{{ translate("USER", 2) }}</span>
+ <span class="info-box-number">{{current_user.group.users.count}}</span>
+ </div>
+ <!-- /.info-box-content -->
+ </div>
+ <!-- /.info-box -->
+ </div>
+ <!-- /.col -->
+ </div>
+ <!-- /.row -->
+ {% endif %}
+
+ <!-- Main panels -->
+ <div class="row">
+ {% if checkAccess('uri_users') or checkAccess('view_system_info') %}
+ <div class="col-md-6 col-sm-12 col-xs-12">
+ {% if checkAccess('uri_users') %}
+ <div class="row">
+ <div class="col-sm-12">
+ <!-- USERS LIST -->
+ <div class="box box-info">
+ <div class="box-header with-border">
+ <h3 class="box-title">{{translate("USER.LATEST")}}</h3>
+ </div>
+ <!-- /.box-header -->
+ <div class="box-body no-padding">
+ <ul class="users-list clearfix">
+ {% for user in users %}
+ <li>
+ <img src="{{ user.avatar }}" alt="User Image">
+ <a class="users-list-name" href="{{site.uri.public}}/users/u/{{user.user_name}}">{{user.first_name}} {{user.last_name}}</a>
+ <span class="users-list-date">{{ user.registered }}</span>
+ </li>
+ {% endfor %}
+ </ul>
+ <!-- /.users-list -->
+ </div>
+ <!-- /.box-body -->
+ <div class="box-footer text-center">
+ <a href="{{site.uri.public}}/users" class="uppercase">{{translate("USER.VIEW_ALL")}}</a>
+ </div>
+ <!-- /.box-footer -->
+ </div>
+ <!--/.box -->
+ </div>
+ <!-- /.col -->
+ </div>
+ <!-- /.row -->
+ {% endif %}
+
+ {% if checkAccess('view_system_info') %}
+ <div class="row">
+ <div class="col-sm-12">
+ <div class="box box-primary">
+ <div class="box-header with-border">
+ <h3 class="box-title">{{translate("SYSTEM_INFO")}}</h3>
+ </div>
+ <!-- /.box-header -->
+ <div class="box-body">
+ <dl class="dl-horizontal">
+ <dt>{{translate("SYSTEM_INFO.UF_VERSION")}}</dt>
+ <dd>{{info.version.UF}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.PHP_VERSION")}}</dt>
+ <dd>{{info.version.php}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.SERVER")}}</dt>
+ <dd>{{info.environment.SERVER_SOFTWARE}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.DB_VERSION")}}</dt>
+ <dd>{{info.version.database.type}} {{info.version.database.version}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.DB_NAME")}}</dt>
+ <dd>{{info.database.name}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.DIRECTORY")}}</dt>
+ <dd>{{info.path.project}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.URL")}}</dt>
+ <dd>{{site.uri.public}}</dd>
+
+ <dt>{{translate("SYSTEM_INFO.SPRINKLES")}}</dt>
+ <dd>
+ <ul class="list-unstyled">
+ {% for sprinkle in sprinkles %}
+ <li>
+ {{sprinkle}}
+ </li>
+ {% endfor %}
+ </ul>
+ </dd>
+ </dl>
+ </div>
+ <!-- /.box-body -->
+ <div class="box-footer text-center">
+ <a href="javascript:void(0)" class="js-clear-cache uppercase">{{ translate("CACHE.CLEAR") }}</a>
+ </div>
+ <!-- /.box-footer -->
+ </div>
+ <!--/.box -->
+ </div>
+ <!-- /.col -->
+ </div>
+ <!-- /.row -->
+ {% endif %}
+ </div>
+ <!-- /.col -->
+ {% endif %}
+
+ {% if checkAccess('uri_activities') %}
+ <div class="col-md-6 col-sm-12 col-xs-12">
+ <div id="widget-activities" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-tasks fa-fw"></i> {{translate('ACTIVITY', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/activities.html.twig" with {
+ "table" : {
+ "id" : "table-activities",
+ "columns" : ["user"]
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ {% elseif checkAccess('view_group_field', {
+ 'group': current_user.group,
+ 'property': 'users'
+ }) %}
+ <div class="col-md-6 col-sm-12 col-xs-12">
+ <div id="widget-group-users" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-user"></i> {{translate('USER', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/users.html.twig" with {
+ "table" : {
+ "id" : "table-group-users"
+ }
+ }
+ %}
+ </div>
+ <div class="box-footer">
+ <button type="button" class="btn btn-success js-user-create">
+ <i class="fa fa-plus-square"></i> {{translate("USER.CREATE")}}
+ </button>
+ </div>
+ </div>
+ </div>
+ {% else %}
+ <div class="col-sm-4 col-sm-offset-4 col-xs-12">
+ <div class="box box-widget widget-user">
+ <!-- Add the bg color to the header using any of the bg-* classes -->
+ <div class="widget-user-header bg-black-active">
+ <h3 class="widget-user-username">
+ {{translate("WELCOME", {
+ 'first_name': current_user.first_name
+ })}}
+ </h3>
+ </div>
+ <div class="widget-user-image">
+ <img class="img-circle" src="{{assets.url('assets://userfrosting/images/cupcake.png')}}" alt="User Avatar">
+ </div>
+ <div class="box-footer">
+ <h4>
+ {{translate("WELCOME_TO", {
+ 'title': site.title
+ })}}
+ </h4>
+ <p>
+ {{translate("NO_FEATURES_YET")}}
+ </p>
+ </div>
+ </div>
+ <!-- /.widget-user -->
+
+
+ </div>
+ <!-- /.row -->
+ {% endif %}
+ </div>
+ <!-- /.row -->
+{% endblock %}
+
+{% block scripts_page %}
+ <!-- Include page variables -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+
+ // Add user name
+ page = $.extend(
+ true, // deep extend
+ {
+ "group_slug": "{{current_user.group.slug}}"
+ },
+ page
+ );
+ </script>
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/dashboard') | raw }}
+
+{% endblock %} \ No newline at end of file
diff --git a/main/app/sprinkles/admin/templates/pages/group.html.twig b/main/app/sprinkles/admin/templates/pages/group.html.twig
new file mode 100755
index 0000000..bf4d275
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/group.html.twig
@@ -0,0 +1,106 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("GROUP", 2) }} | {{group.name}}{% endblock %}
+
+{% block page_description %}{{ translate("GROUP.INFO_PAGE", {name: group.name}) }}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-lg-4">
+ <div id="view-group" class="box box-primary">
+ <div class="box-header with-border">
+ <h3 class="box-title">{{translate('GROUP.SUMMARY')}}</h3>
+ {% if 'tools' not in tools.hidden %}
+ <div class="box-tools pull-right">
+ <div class="btn-group">
+ <button type="button" class="btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <i class="fa fa-gear"></i> <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu box-tool-menu">
+ {% block tools %}
+ <li>
+ <a href="#" class="js-group-edit" data-slug="{{group.slug}}">
+ <i class="fa fa-edit fa-fw"></i> {{translate('EDIT')}}
+ </a>
+ </li>
+ {% if 'delete' not in tools.hidden %}
+ <li>
+ <a href="#" class="js-group-delete" data-slug="{{group.slug}}">
+ <i class="fa fa-trash-o fa-fw"></i> {{translate('DELETE')}}
+ </a>
+ </li>
+ {% endif %}
+ {% endblock %}
+ </ul>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ <div class="box-body box-profile">
+ <div class="text-center">
+ <i class="{{group.icon}} fa-5x"></i>
+ </div>
+
+ <h3 class="profile-username text-center">{{group.name}}</h3>
+
+ {% if 'description' not in fields.hidden %}
+ <p class="text-muted">
+ {{group.description}}
+ </p>
+ {% endif %}
+ {% if 'users' not in fields.hidden %}
+ <hr>
+ <strong><i class="fa fa-users margin-r-5"></i> {{ translate('USER', 2)}}</strong>
+ <p class="badge bg-blue box-profile-property">
+ {{group.users.count}}
+ </p>
+ {% endif %}
+ {% block group_profile %}{% endblock %}
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-8">
+ <div id="widget-group-users" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-user"></i> {{translate('USER', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/users.html.twig" with {
+ "table" : {
+ "id" : "table-group-users"
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include page variables -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+
+ // Add user name
+ page = $.extend(
+ true, // deep extend
+ {
+ "group_slug": "{{group.slug}}"
+ },
+ page
+ );
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/group') | raw }}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/groups.html.twig b/main/app/sprinkles/admin/templates/pages/groups.html.twig
new file mode 100755
index 0000000..35e9a88
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/groups.html.twig
@@ -0,0 +1,52 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("GROUP", 2) }}{% endblock %}
+
+{% block page_description %}{{ translate("GROUP.PAGE_DESCRIPTION") }}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-md-12">
+ <div id="widget-groups" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-users"></i> {{translate('GROUP', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/groups.html.twig" with {
+ "table" : {
+ "id" : "table-groups"
+ }
+ }
+ %}
+ </div>
+ {% if checkAccess('create_group') %}
+ <div class="box-footer">
+ <button type="button" class="btn btn-success js-group-create">
+ <i class="fa fa-plus-square"></i> {{translate("GROUP.CREATE")}}
+ </button>
+ </div>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include validation rules -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/groups') | raw }}
+
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/permission.html.twig b/main/app/sprinkles/admin/templates/pages/permission.html.twig
new file mode 100755
index 0000000..6adc014
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/permission.html.twig
@@ -0,0 +1,91 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-group-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{translate("PERMISSION", 2)}} | {{permission.name}}{% endblock %}
+
+{% block page_description %}{{translate("PERMISSION.INFO_PAGE", {name: permission.name})}}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-lg-4">
+ <div id="view-permission" class="box box-primary">
+ <div class="box-header with-border">
+ <h3 class="box-title">{{translate("PERMISSION.SUMMARY")}}</h3>
+ </div>
+ <div class="box-body box-profile">
+ <div class="text-center">
+ <i class="fa fa-key fa-5x"></i>
+ </div>
+
+ <h3 class="profile-username text-center">{{permission.name}}</h3>
+
+ <p class="text-muted">
+ {{permission.description}}
+ </p>
+ <hr>
+ <strong>{{translate("PERMISSION.ID")}}: </strong>
+ <span class="js-copy-container">
+ <span class="js-copy-target" style="margin: 0 5px;">{{permission.id}}</span>
+ <i class="fa fa-copy text-blue uf-copy-trigger js-copy-trigger"></i>
+ </span>
+ <hr>
+ <strong>{{translate("SLUG_CONDITION")}}</strong>
+ <br>
+ <br>
+ <p>
+ <code>{{permission.slug}}</code>
+ </p>
+ <p>
+ ↳ <code>{{permission.conditions}}</code>
+ </p>
+ </div>
+ <div class="box-footer">
+ {{translate("PERMISSION.NOTE_READ_ONLY")}}
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-8">
+ <div id="widget-permission-users" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-key"></i> {{translate("USER.WITH_PERMISSION")}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/users.html.twig" with {
+ "table" : {
+ "id" : "table-permission-users",
+ "columns" : ["via_roles"]
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include page variables -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+
+ // Add user name
+ page = $.extend(
+ true, // deep extend
+ {
+ "permission_id": "{{permission.id}}"
+ },
+ page
+ );
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/permission') | raw }}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/permissions.html.twig b/main/app/sprinkles/admin/templates/pages/permissions.html.twig
new file mode 100755
index 0000000..2696209
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/permissions.html.twig
@@ -0,0 +1,45 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("PERMISSION", 2)}}{% endblock %}
+
+{% block page_description %}{{ translate("PERMISSION.PAGE_DESCRIPTION")}}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-md-12">
+ <div id="widget-permissions" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title pull-left"><i class="fa fa-key fa-fw"></i> {{translate('PERMISSION', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/permissions.html.twig" with {
+ "table" : {
+ "id" : "table-permissions"
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include validation rules -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/permissions') | raw }}
+
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/role.html.twig b/main/app/sprinkles/admin/templates/pages/role.html.twig
new file mode 100755
index 0000000..daf1004
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/role.html.twig
@@ -0,0 +1,129 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-group-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{translate("ROLE", 2)}} | {{role.name}}{% endblock %}
+
+{% block page_description %}{{translate("ROLE.INFO_PAGE", {name: role.name})}}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-lg-4">
+ <div id="view-role" class="box box-primary">
+ <div class="box-header with-border">
+ <h3 class="box-title">{{translate('ROLE.SUMMARY')}}</h3>
+ {% if 'tools' not in tools.hidden %}
+ <div class="box-tools pull-right">
+ <div class="btn-group">
+ <button type="button" class="btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <i class="fa fa-gear"></i> <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu box-tool-menu">
+ {% block tools %}
+ <li>
+ <a href="#" class="js-role-edit" data-slug="{{role.slug}}">
+ <i class="fa fa-edit fa-fw"></i> {{translate('EDIT')}}
+ </a>
+ </li>
+ {% if 'permissions' not in tools.hidden %}
+ <li>
+ <a href="#" class="js-role-permissions" data-slug="{{role.slug}}">
+ <i class="fa fa-key"></i> {{translate("PERMISSION.MANAGE")}}
+ </a>
+ </li>
+ {% endif %}
+ {% if 'delete' not in tools.hidden %}
+ <li>
+ <a href="#" class="js-role-delete" data-slug="{{role.slug}}">
+ <i class="fa fa-trash-o fa-fw"></i> {{translate('DELETE')}}
+ </a>
+ </li>
+ {% endif %}
+ {% endblock %}
+ </ul>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ <div class="box-body box-profile">
+ <div class="text-center">
+ <i class="fa fa-drivers-license fa-5x"></i>
+ </div>
+
+ <h3 class="profile-username text-center">{{role.name}}</h3>
+
+ {% if 'description' not in fields.hidden %}
+ <p class="text-muted">
+ {{role.description}}
+ </p>
+ {% endif %}
+ {% if 'users' not in fields.hidden %}
+ <hr>
+ <strong><i class="fa fa-users margin-r-5"></i> {{ translate('USER', 2)}}</strong>
+ <p class="badge bg-blue box-profile-property">
+ {{role.users.count}}
+ </p>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-8">
+ <div id="widget-role-permissions" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-key"></i> {{translate('PERMISSION', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/permissions.html.twig" with {
+ "table" : {
+ "id" : "table-role-permissions"
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-12">
+ <div id="widget-role-users" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-key"></i> {{translate('USER', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/users.html.twig" with {
+ "table" : {
+ "id" : "table-role-users",
+ "columns" : ["last_activity"]
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include page variables -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+
+ // Add user name
+ page = $.extend(
+ true, // deep extend
+ {
+ "role_slug": "{{role.slug}}"
+ },
+ page
+ );
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/role') | raw }}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/roles.html.twig b/main/app/sprinkles/admin/templates/pages/roles.html.twig
new file mode 100755
index 0000000..c5b3995
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/roles.html.twig
@@ -0,0 +1,50 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("ROLE", 2)}}{% endblock %}
+
+{% block page_description %}{{ translate("ROLE.PAGE_DESCRIPTION")}}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-md-12">
+ <div id="widget-roles" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-drivers-license"></i> {{translate('ROLE', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="panel-body">
+ {% include "tables/roles.html.twig" with {
+ "table" : {
+ "id" : "table-roles"
+ }
+ }
+ %}
+ </div>
+ <div class="box-footer">
+ <button type="button" class="btn btn-success js-role-create">
+ <i class="fa fa-plus-square"></i> {{translate("ROLE.CREATE")}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include validation rules -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/roles') | raw }}
+
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/user.html.twig b/main/app/sprinkles/admin/templates/pages/user.html.twig
new file mode 100755
index 0000000..d9c9ab2
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/user.html.twig
@@ -0,0 +1,195 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("USER", 2)}} | {{user.full_name}}{% endblock %}
+
+{% block page_description %}{{ translate("USER.INFO_PAGE", {name: user.user_name}) }}{% endblock %}
+
+{% block body_matter %}
+ {% block group_box %}
+ {% endblock %}
+ <div class="row">
+ <div class="col-lg-4">
+ <div id="view-user">
+ {% block user_box %}
+ <div class="box box-primary">
+ <div class="box-header with-border">
+ <h3 class="box-title">{{translate('USER.SUMMARY')}}</h3>
+ {% if 'tools' not in tools.hidden %}
+ <div class="box-tools pull-right">
+ <div class="btn-group">
+ <button type="button" class="btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <i class="fa fa-gear"></i> <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu box-tool-menu">
+ {% block tools %}
+ <li>
+ <a href="#" class="js-user-edit" data-user_name="{{user.user_name}}">
+ <i class="fa fa-edit fa-fw"></i> {{translate('EDIT')}}
+ </a>
+ </li>
+ {% if 'password' not in tools.hidden %}
+ <li>
+ <a href="#" class="js-user-password" data-user_name="{{user.user_name}}">
+ <i class="fa fa-lock fa-fw"></i> {{translate('PASSWORD')}}
+ </a>
+ </li>
+ {% endif %}
+ {% if 'roles' not in tools.hidden %}
+ <li>
+ <a href="#" class="js-user-roles" data-user_name="{{user.user_name}}">
+ <i class="fa fa-drivers-license fa-fw"></i> {{translate('ROLE', 2)}}
+ </a>
+ </li>
+ {% endif %}
+ {% if 'activate' not in tools.hidden and user.flag_verified == "0" %}
+ <li>
+ <a href="#" class="js-user-activate" data-user_name="{{user.user_name}}">
+ <i class="fa fa-bolt fa-fw"></i> {{translate('ACTIVATE')}}
+ </a>
+ </li>
+ {% endif %}
+ {% if 'enable' not in tools.hidden %}
+ {% if user.flag_enabled == "1" %}
+ <li>
+ <a href="#" class="js-user-disable" data-user_name="{{user.user_name}}">
+ <i class="fa fa-minus-circle fa-fw"></i> {{translate('DISABLE')}}
+ </a>
+ </li>
+ {% else %}
+ <li>
+ <a href="#" class="js-user-enable" data-user_name="{{user.user_name}}">
+ <i class="fa fa-plus-circle fa-fw"></i> {{translate('ENABLE')}}
+ </a>
+ </li>
+ {% endif %}
+ {% endif %}
+ {% if 'delete' not in tools.hidden %}
+ <li>
+ <a href="#" class="js-user-delete" data-user_name="{{user.user_name}}">
+ <i class="fa fa-trash-o fa-fw"></i> {{translate('DELETE')}}
+ </a>
+ </li>
+ {% endif %}
+ {% endblock %}
+ </ul>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ <div class="box-body box-profile">
+ <img class="profile-user-img img-responsive img-circle" src="{{user.avatar}}" alt="{{user.user_name}}">
+
+ <h3 class="profile-username text-center">{{user.full_name}}</h3>
+ <div class="text-center">
+ {% if user.flag_enabled == 0 %}
+ <i class="fa fa-fw fa-minus-circle fa-lg text-red" title="{{translate('DISABLED')}}"></i>
+ {% endif %}
+ {% if user.flag_verified == 0 %}
+ <i class="fa fa-fw fa-bolt fa-lg text-yellow" title="{{translate('UNACTIVATED')}}"></i>
+ {% endif %}
+ </div>
+ <h4 class="text-muted text-center">{{user.user_name}}{% if 'group' not in fields.hidden %} • {{user.group.name}}{% endif %}</h4>
+
+ {% if 'email' not in fields.hidden %}
+ <hr>
+ <strong><i class="fa fa-envelope margin-r-5"></i> {{translate("EMAIL")}}</strong>
+ <p class="text-muted box-profile-property js-copy-container">
+ <i class="fa fa-copy uf-copy-trigger js-copy-trigger"></i>
+ <span class="js-copy-target">{{user.email}}</span>
+ </p>
+ {% endif %}
+
+ {% if 'locale' not in fields.hidden %}
+ <hr>
+ <strong><i class="fa fa-language margin-r-5"></i> {{translate("LOCALE")}}</strong>
+ <p class="text-muted box-profile-property">
+ {{locales[user.locale]}}
+ </p>
+ {% endif %}
+
+ {% block user_profile %}{% endblock %}
+
+ {% if 'roles' not in fields.hidden %}
+ <hr>
+ <strong><i class="fa fa-drivers-license margin-r-5"></i> {{translate("ROLE", 2)}}</strong>
+ <p class="box-profile-property">
+ {% for role in user.roles %}
+ <span class="label label-primary" title="{{role.description}}">{{role.name}}</span>
+ {% endfor %}
+ </p>
+ {% endif %}
+ </div>
+ </div>
+ {% endblock %}
+ </div>
+ </div>
+ {% if 'activities' not in widgets.hidden %}
+ <div class="col-lg-8">
+ {% block activity_box %}
+ <div id="widget-user-activities" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title"><i class="fa fa-fw fa-tasks"></i> {{translate('ACTIVITY', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/activities.html.twig" with {
+ "table" : {
+ "id" : "table-user-activities"
+ }
+ }
+ %}
+ </div>
+ </div>
+ {% endblock %}
+ </div>
+ {% endif %}
+ </div>
+ {% if 'permissions' not in widgets.hidden %}
+ <div class="row">
+ <div class="col-md-12">
+ <div id="widget-permissions" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title pull-left"><i class="fa fa-key fa-fw"></i> {{translate('PERMISSION', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/permissions.html.twig" with {
+ "table" : {
+ "id" : "table-permissions",
+ "columns" : ["via_roles"]
+ }
+ }
+ %}
+ </div>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include page variables -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+
+ // Add user name
+ page = $.extend(
+ true, // deep extend
+ {
+ "user_name": "{{user.user_name}}"
+ },
+ page
+ );
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/user') | raw }}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/pages/users.html.twig b/main/app/sprinkles/admin/templates/pages/users.html.twig
new file mode 100755
index 0000000..3e4642d
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/pages/users.html.twig
@@ -0,0 +1,53 @@
+{% extends "pages/abstract/dashboard.html.twig" %}
+
+{% block stylesheets_page %}
+ <!-- Page-specific CSS asset bundle -->
+ {{ assets.css('css/form-widgets') | raw }}
+{% endblock %}
+
+{# Overrides blocks in head of base template #}
+{% block page_title %}{{ translate("USER", 2)}}{% endblock %}
+
+{% block page_description %}{{ translate("USER.PAGE_DESCRIPTION")}}{% endblock %}
+
+{% block body_matter %}
+ <div class="row">
+ <div class="col-md-12">
+ <div id="widget-users" class="box box-primary">
+ <div class="box-header">
+ <h3 class="box-title pull-left"><i class="fa fa-fw fa-user"></i> {{translate('USER', 2)}}</h3>
+ {% include "tables/table-tool-menu.html.twig" %}
+ </div>
+ <div class="box-body">
+ {% include "tables/users.html.twig" with {
+ "table" : {
+ "id" : "table-users",
+ "columns" : ["last_activity"]
+ }
+ }
+ %}
+ </div>
+ {% if checkAccess('create_user') %}
+ <div class="box-footer">
+ <button type="button" class="btn btn-success js-user-create">
+ <i class="fa fa-plus-square"></i> {{ translate("USER.CREATE")}}
+ </button>
+ </div>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+{% endblock %}
+{% block scripts_page %}
+ <!-- Include validation rules -->
+ <script>
+ {% include "pages/partials/page.js.twig" %}
+ </script>
+
+ <!-- Include form widgets JS -->
+ {{ assets.js('js/form-widgets') | raw }}
+
+ <!-- Include page-specific JS -->
+ {{ assets.js('js/pages/users') | raw }}
+
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/tables/activities.html.twig b/main/app/sprinkles/admin/templates/tables/activities.html.twig
new file mode 100755
index 0000000..d70541b
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/tables/activities.html.twig
@@ -0,0 +1,73 @@
+{# This partial template renders a table of user activities, to be populated with rows via an AJAX request.
+ # This extends a generic template for paginated tables.
+ #
+ # Note that this template contains a "skeleton" table with an empty table body, and then a block of Handlebars templates which are used
+ # to render the table cells with the data from the AJAX request.
+#}
+
+{% extends "tables/table-paginated.html.twig" %}
+
+{% block table %}
+ <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="[[0, 1]]">
+ <thead>
+ <tr>
+ <th class="sorter-metanum" data-column-name="occurred_at" data-column-template="#activity-table-column-occurred-at" data-priority="1">{{translate('ACTIVITY.TIME')}} <i class="fa fa-sort"></i></th>
+ {% if 'user' in table.columns %}
+ <th class="sorter-metatext" data-column-name="user" data-column-template="#activity-table-column-user" data-priority="1">{{translate('USER')}} <i class="fa fa-sort"></i></th>
+ {% endif %}
+ <th class="sorter-metatext" data-column-name="description" data-column-template="#activity-table-column-description" data-priority="1">{{translate("DESCRIPTION")}} <i class="fa fa-sort"></i></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+{% endblock %}
+
+{% block table_cell_templates %}
+ {# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+ #}
+ {% verbatim %}
+ <script id="activity-table-column-occurred-at" type="text/x-handlebars-template">
+ {{#if row.occurred_at }}
+ <td data-num="{{dateFormat row.occurred_at format='x'}}">
+ {{dateFormat row.occurred_at format="dddd"}}<br>{{dateFormat row.occurred_at format="MMM Do, YYYY h:mm a"}}
+ </td>
+ {{ else }}
+ <td data-num="0">
+ <i>{% endverbatim %}{{translate("UNKNOWN")}}{% verbatim %}</i>
+ </td>
+ {{/if }}
+ </script>
+
+ <script id="activity-table-column-user" type="text/x-handlebars-template">
+ <td data-text="{{row.user.last_name}}">
+ {{#if row.user }}
+ <strong>
+ <a href="{{site.uri.public}}/users/u/{{row.user.user_name}}">{{row.user.first_name}} {{row.user.last_name}} ({{row.user.user_name}})</a>
+ </strong>
+ <div class="js-copy-container">
+ <span class="js-copy-target">{{row.user.email}}</span>
+ <button class="btn btn-xs uf-copy-trigger js-copy-trigger"><i class="fa fa-copy"></i></button>
+ </div>
+ {{ else }}
+ <i>{% endverbatim %}{{translate("USER.DELETED")}}{% verbatim %}</i>
+ {{/if }}
+ </td>
+ </script>
+
+ <script id="activity-table-column-description" type="text/x-handlebars-template">
+ <td>
+ <div>
+ {{row.ip_address}}
+ </div>
+ <div>
+ <i>{{row.description}}</i>
+ </div>
+ </td>
+ </script>
+ {% endverbatim %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/tables/groups.html.twig b/main/app/sprinkles/admin/templates/tables/groups.html.twig
new file mode 100755
index 0000000..2c5a84a
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/tables/groups.html.twig
@@ -0,0 +1,69 @@
+{# This partial template renders a table of groups, to be populated with rows via an AJAX request.
+ # This extends a generic template for paginated tables.
+ #
+ # Note that this template contains a "skeleton" table with an empty table body, and then a block of Handlebars templates which are used
+ # to render the table cells with the data from the AJAX request.
+#}
+
+{% extends "tables/table-paginated.html.twig" %}
+
+{% block table %}
+ <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
+ <thead>
+ <tr>
+ <th class="sorter-metatext" data-column-name="name" data-column-template="#group-table-column-info" data-priority="1">{{translate('GROUP')}} <i class="fa fa-sort"></i></th>
+ <th class="sorter-metatext" data-column-name="description" data-column-template="#group-table-column-description" data-priority="2">{{translate("DESCRIPTION")}} <i class="fa fa-sort"></i></th>
+ <th data-column-template="#group-table-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate("ACTIONS")}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+{% endblock %}
+
+{% block table_cell_templates %}
+ {# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+ #}
+ {% verbatim %}
+ <script id="group-table-column-info" type="text/x-handlebars-template">
+ <td data-text="{{row.name}}">
+ <strong>
+ <i class="{{row.icon}} fa-fw"></i> <a href="{{site.uri.public}}/groups/g/{{row.slug}}">{{row.name}}</a>
+ </strong>
+ </td>
+ </script>
+
+ <script id="group-table-column-description" type="text/x-handlebars-template">
+ <td>
+ {{row.description}}
+ </td>
+ </script>
+
+ <script id="group-table-column-actions" type="text/x-handlebars-template">
+ <td>
+ <div class="btn-group">
+ <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
+ {% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right" role="menu">
+ <li>
+ <a href="#" data-slug="{{row.slug}}" class="js-group-edit">
+ <i class="fa fa-edit"></i> {% endverbatim %}{{translate("GROUP.EDIT")}}{% verbatim %}
+ </a>
+ </li>
+ <li>
+ <a href="#" data-slug="{{row.slug}}" class="js-group-delete">
+ <i class="fa fa-trash-o"></i> {% endverbatim %}{{translate("GROUP.DELETE")}}{% verbatim %}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </script>
+ {% endverbatim %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/tables/permissions.html.twig b/main/app/sprinkles/admin/templates/tables/permissions.html.twig
new file mode 100755
index 0000000..92e236a
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/tables/permissions.html.twig
@@ -0,0 +1,66 @@
+{# This partial template renders a table of permissions, to be populated with rows via an AJAX request.
+ # This extends a generic template for paginated tables.
+ #
+ # Note that this template contains a "skeleton" table with an empty table body, and then a block of Handlebars templates which are used
+ # to render the table cells with the data from the AJAX request.
+#}
+
+{% extends "tables/table-paginated.html.twig" %}
+
+{% block table %}
+ <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
+ <thead>
+ <tr>
+ <th class="sorter-metatext" data-column-name="name" data-column-template="#permission-table-column-name" data-priority="1">{{translate('PERMISSION')}} <i class="fa fa-sort"></i></th>
+ <th class="sorter-metatext" data-column-name="properties" data-column-template="#permission-table-column-properties" data-priority="1">{{translate('SLUG_CONDITION')}} <i class="fa fa-sort"></i></th>
+ {% if 'via_roles' in table.columns %}
+ <th data-column-template="#permission-table-column-via-roles" data-sorter="false" data-filter="false" data-priority="2">{{translate('PERMISSION.VIA_ROLES')}}</th>
+ {% endif %}
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+{% endblock %}
+
+{% block table_cell_templates %}
+ {# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+ #}
+ {% verbatim %}
+ <script id="permission-table-column-name" type="text/x-handlebars-template">
+ <td data-text="{{row.name}}">
+ <div>
+ <strong>
+ <a href="{{site.uri.public}}/permissions/p/{{row.id}}">{{row.name}}</a>
+ </strong>
+ </div>
+ </td>
+ </script>
+
+ <script id="permission-table-column-properties" type="text/x-handlebars-template">
+ <td>
+ <div>
+ <code>{{row.slug}}</code>
+ </div>
+ <div>
+ ↳ <code>{{row.conditions}}</code>
+ </div>
+ <div>
+ <i>{{row.description}}</i>
+ </div>
+ </td>
+ </script>
+
+ <script id="permission-table-column-via-roles" type="text/x-handlebars-template">
+ <td>
+ {{#each row.roles_via }}
+ <a href="{% endverbatim %}{# Handlebars can't access variables in the global scope, so we have to use Twig to insert the base url #}{{site.uri.public}}{% verbatim %}/roles/r/{{this.slug}}" class="label label-primary" title="{{this.description}}">{{this.name}}</a>
+ {{/each}}
+ </td>
+ </script>
+ {% endverbatim %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/tables/roles.html.twig b/main/app/sprinkles/admin/templates/tables/roles.html.twig
new file mode 100755
index 0000000..dbdb49e
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/tables/roles.html.twig
@@ -0,0 +1,74 @@
+{# This partial template renders a table of roles, to be populated with rows via an AJAX request.
+ # This extends a generic template for paginated tables.
+ #
+ # Note that this template contains a "skeleton" table with an empty table body, and then a block of Handlebars templates which are used
+ # to render the table cells with the data from the AJAX request.
+#}
+
+{% extends "tables/table-paginated.html.twig" %}
+
+{% block table %}
+ <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
+ <thead>
+ <tr>
+ <th class="sorter-metatext" data-column-name="name" data-column-template="#role-table-column-info" data-priority="1">{{translate('ROLE')}} <i class="fa fa-sort"></i></th>
+ <th class="sorter-metatext" data-column-name="description" data-column-template="#role-table-column-description" data-priority="2">{{translate('DESCRIPTION')}} <i class="fa fa-sort"></i></th>
+ <th data-column-template="#role-table-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate('ACTIONS')}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+{% endblock %}
+
+{% block table_cell_templates %}
+ {# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+ #}
+ {% verbatim %}
+ <script id="role-table-column-info" type="text/x-handlebars-template">
+ <td data-text="{{row.name}}">
+ <strong>
+ <a href="{{site.uri.public}}/roles/r/{{row.slug}}">{{row.name}}</a>
+ </strong>
+ </td>
+ </script>
+
+ <script id="role-table-column-description" type="text/x-handlebars-template">
+ <td>
+ {{row.description}}
+ </td>
+ </script>
+
+ <script id="role-table-column-actions" type="text/x-handlebars-template">
+ <td>
+ <div class="btn-group">
+ <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
+ {% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right" role="menu">
+ <li>
+ <a href="#" data-slug="{{row.slug}}" class="js-role-permissions">
+ <i class="fa fa-key"></i> {% endverbatim %}{{translate("PERMISSION.MANAGE")}}{% verbatim %}
+ </a>
+ </li>
+ <li>
+ <a href="#" data-slug="{{row.slug}}" class="js-role-edit">
+ <i class="fa fa-edit"></i> {% endverbatim %}{{translate("ROLE.EDIT")}}{% verbatim %}
+ </a>
+ </li>
+ <li>
+ <a href="#" data-slug="{{row.slug}}" class="js-role-delete">
+ <i class="fa fa-trash-o"></i> {% endverbatim %}{{translate("ROLE.DELETE")}}{% verbatim %}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </script>
+ {% endverbatim %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/templates/tables/users.html.twig b/main/app/sprinkles/admin/templates/tables/users.html.twig
new file mode 100755
index 0000000..1cebb47
--- /dev/null
+++ b/main/app/sprinkles/admin/templates/tables/users.html.twig
@@ -0,0 +1,149 @@
+{# This partial template renders a table of users, to be populated with rows via an AJAX request.
+ # This extends a generic template for paginated tables.
+ #
+ # Note that this template contains a "skeleton" table with an empty table body, and then a block of Handlebars templates which are used
+ # to render the table cells with the data from the AJAX request.
+#}
+
+{% extends "tables/table-paginated.html.twig" %}
+
+{% block table %}
+ <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
+ <thead>
+ <tr>
+ <th class="sorter-metatext" data-column-name="name" data-column-template="#user-table-column-info" data-priority="1">{{translate('USER')}} <i class="fa fa-sort"></i></th>
+ {% if 'last_activity' in table.columns %}
+ <th class="sorter-metanum" data-column-name="last_activity" data-column-template="#user-table-column-last-activity" data-priority="3">{{translate("ACTIVITY.LAST")}} <i class="fa fa-sort"></i></th>
+ {% endif %}
+ {% if 'via_roles' in table.columns %}
+ <th data-column-template="#user-table-column-via-roles" data-sorter="false" data-filter="false" data-priority="1">{{translate('PERMISSION.VIA_ROLES')}}</th>
+ {% endif %}
+ <th class="filter-select filter-metatext" data-column-name="status" data-column-template="#user-table-column-status" data-priority="2">{{translate("STATUS")}} <i class="fa fa-sort"></i></th>
+ <th data-column-name="actions" data-column-template="#user-table-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate("ACTIONS")}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+{% endblock %}
+
+{% block table_cell_templates %}
+ {# This contains a series of <script> blocks, each of which is a client-side Handlebars template.
+ # Note that these are NOT Twig templates, although the syntax is similar. We wrap them in the `verbatim` tag,
+ # so that Twig will output them directly into the DOM instead of trying to treat them like Twig templates.
+ #
+ # These templates require handlebars-helpers.js, moment.js
+ #}
+ {% verbatim %}
+ <script id="user-table-column-info" type="text/x-handlebars-template">
+ <td data-text="{{row.last_name}}">
+ <strong>
+ <a href="{{site.uri.public}}/users/u/{{row.user_name}}">{{row.first_name}} {{row.last_name}} ({{row.user_name}})</a>
+ </strong>
+ <div class="js-copy-container">
+ <span class="js-copy-target">{{row.email}}</span>
+ <button class="btn btn-xs uf-copy-trigger js-copy-trigger"><i class="fa fa-copy"></i></button>
+ </div>
+ </td>
+ </script>
+
+ <script id="user-table-column-last-activity" type="text/x-handlebars-template">
+ {{#if row.last_activity }}
+ <td data-num="{{dateFormat row.last_activity.occurred_at format='x'}}">
+ {{dateFormat row.last_activity.occurred_at format="dddd"}}<br>{{dateFormat row.last_activity.occurred_at format="MMM Do, YYYY h:mm a"}}
+ <br>
+ <i>{{row.last_activity.description}}</i>
+ </td>
+ {{ else }}
+ <td data-num="0">
+ <i>{% endverbatim %}{{translate("UNKNOWN")}}{% verbatim %}</i>
+ </td>
+ {{/if }}
+ </script>
+
+ <script id="user-table-column-status" type="text/x-handlebars-template">
+ <td
+ {{#ifx row.flag_enabled '==' 0 }}
+ data-text="disabled"
+ {{ else }}
+ {{#ifx row.flag_verified '==' 0 }}
+ data-text="unactivated"
+ {{ else }}
+ data-text="active"
+ {{/ifx }}
+ {{/ifx }}
+ >
+ {{#ifx row.flag_enabled '==' 0 }}
+ <span class="text-muted">
+ {% endverbatim %}{{translate("DISABLED")}}{% verbatim %}
+ </span>
+ {{ else }}
+ {{#ifx row.flag_verified '==' 0 }}
+ <span class="text-yellow">
+ {% endverbatim %}{{translate("UNACTIVATED")}}{% verbatim %}
+ </span>
+ {{ else }}
+ <span>
+ {% endverbatim %}{{translate("ACTIVE")}}{% verbatim %}
+ </span>
+ {{/ifx }}
+ {{/ifx }}
+ </td>
+ </script>
+ <script id="user-table-column-actions" type="text/x-handlebars-template">
+ <td class="uf-table-fit-width">
+ <div class="btn-group">
+ <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}<span class="caret"></span></button>
+ <ul class="dropdown-menu dropdown-menu-right-responsive" role="menu">
+ {{#ifx row.flag_verified '==' 0 }}
+ <li>
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-activate">
+ <i class="fa fa-bolt"></i> {% endverbatim %}{{translate("USER.ACTIVATE")}}{% verbatim %}
+ </a>
+ </li>
+ {{/ifx }}
+ <li>
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-edit">
+ <i class="fa fa-edit"></i> {% endverbatim %}{{translate("USER.EDIT")}}{% verbatim %}
+ </a>
+ </li>
+ <li>
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-roles">
+ <i class="fa fa-drivers-license"></i> {% endverbatim %}{{translate("ROLE.MANAGE")}}{% verbatim %}
+ </a>
+ </li>
+ <li>
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-password">
+ <i class="fa fa-key"></i> {% endverbatim %}{{translate("USER.ADMIN.CHANGE_PASSWORD")}}{% verbatim %}
+ </a>
+ </li>
+ <li>
+ {{#ifx row.flag_enabled '==' 1 }}
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-disable">
+ <i class="fa fa-minus-circle"></i> {% endverbatim %}{{translate("USER.DISABLE")}}{% verbatim %}
+ </a>
+ {{ else }}
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-enable">
+ <i class="fa fa-plus-circle"></i> {% endverbatim %}{{translate("USER.ENABLE")}}{% verbatim %}
+ </a>
+ {{/ifx }}
+ </li>
+ <li>
+ <a href="#" data-user_name="{{row.user_name}}" class="js-user-delete">
+ <i class="fa fa-trash-o"></i> {% endverbatim %}{{translate("USER.DELETE")}}{% verbatim %}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </script>
+
+ <script id="user-table-column-via-roles" type="text/x-handlebars-template">
+ <td>
+ {{#each row.roles_via }}
+ <a href="{% endverbatim %}{# Handlebars can't access variables in the global scope, so we have to use Twig to insert the base url #}{{site.uri.public}}{% verbatim %}/roles/r/{{this.slug}}" class="label label-primary" title="{{this.description}}">{{this.name}}</a>
+ {{/each}}
+ </td>
+ </script>
+ {% endverbatim %}
+{% endblock %}
diff --git a/main/app/sprinkles/admin/tests/Integration/SprunjeTests.php b/main/app/sprinkles/admin/tests/Integration/SprunjeTests.php
new file mode 100755
index 0000000..9eb4122
--- /dev/null
+++ b/main/app/sprinkles/admin/tests/Integration/SprunjeTests.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace UserFrosting\Tests\Integration;
+
+use Exception;
+use Illuminate\Database\Capsule\Manager as DB;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use UserFrosting\Sprinkle\Admin\Sprunje\UserPermissionSprunje;
+use UserFrosting\Sprinkle\Core\Util\ClassMapper;
+use UserFrosting\Tests\DatabaseTransactions;
+use UserFrosting\Tests\TestCase;
+
+/**
+ * Integration tests for the built-in Sprunje classes.
+ */
+class SprunjeTests extends TestCase
+{
+ use DatabaseTransactions;
+
+ protected $classMapper;
+
+ /**
+ * Setup the database schema.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->classMapper = new ClassMapper();
+ }
+
+ /**
+ * Tests...
+ */
+ public function testUserPermissionSprunje()
+ {
+ $fm = $this->ci->factory;
+
+ // Generate some test models
+ $users = $fm->seed(3, 'UserFrosting\Sprinkle\Account\Database\Models\User');
+ $roles = $fm->seed(3, 'UserFrosting\Sprinkle\Account\Database\Models\Role');
+ $permissions = $fm->seed(3, 'UserFrosting\Sprinkle\Account\Database\Models\Permission');
+
+ // Create some relationships
+ $roles[0]->permissions()->attach($permissions[1]);
+ $roles[0]->permissions()->attach($permissions[2]);
+ $roles[1]->permissions()->attach($permissions[2]);
+ $roles[2]->permissions()->attach($permissions[0]);
+ $roles[2]->permissions()->attach($permissions[1]);
+
+ $users[0]->roles()->attach($roles[1]);
+ $users[0]->roles()->attach($roles[2]);
+ $users[1]->roles()->attach($roles[0]);
+ $users[1]->roles()->attach($roles[1]);
+ $users[2]->roles()->attach($roles[1]);
+
+ $this->classMapper->setClassMapping('user', 'UserFrosting\Sprinkle\Account\Database\Models\User');
+
+ // Test user 0
+ $sprunje = new UserPermissionSprunje($this->classMapper, [
+ 'user_id' => $users[0]->id
+ ]);
+
+ list($count, $countFiltered, $models) = $sprunje->getModels();
+
+ // Check that counts are correct
+ $this->assertEquals(count($models), $count);
+ $this->assertEquals(count($models), $countFiltered);
+
+ // Ignore pivot and roles_via. These are covered by the tests for the relationships themselves.
+ static::ignoreRelations($models);
+ $this->assertCollectionsSame(collect($permissions), $models);
+
+ // Test user 1
+ $sprunje = new UserPermissionSprunje($this->classMapper, [
+ 'user_id' => $users[1]->id
+ ]);
+
+ list($count, $countFiltered, $models) = $sprunje->getModels();
+
+ // Check that counts are correct
+ $this->assertEquals(count($models), $count);
+ $this->assertEquals(count($models), $countFiltered);
+
+ // Ignore pivot and roles_via. These are covered by the tests for the relationships themselves.
+ static::ignoreRelations($models);
+ $this->assertCollectionsSame(collect([
+ $permissions[1],
+ $permissions[2]
+ ]), $models);
+
+ // Test user 2
+ $sprunje = new UserPermissionSprunje($this->classMapper, [
+ 'user_id' => $users[2]->id
+ ]);
+
+ list($count, $countFiltered, $models) = $sprunje->getModels();
+
+ // Check that counts are correct
+ $this->assertEquals(count($models), $count);
+ $this->assertEquals(count($models), $countFiltered);
+
+ // Ignore pivot and roles_via. These are covered by the tests for the relationships themselves.
+ static::ignoreRelations($models);
+ $this->assertCollectionsSame(collect([
+ $permissions[2]
+ ]), $models);
+ }
+}