" : "");
// handle disabled fields
- if (substr($name, 0, 1) == '-') {
- $name = substr($name, 1);
+ if (substr(($name ?? ""), 0, 1) == '-') {
+ $name = substr(($name ?? ""), 1);
$readonly = " readonly='true' ";
}
@@ -969,7 +969,7 @@ public static function autocomplete($name, $options, $value = null, $class = nul
}
}
// Remove trailing comma
- $source = substr($source, 0, -1);
+ $source = substr(($source ?? ""), 0, -1);
$source .= "]";
} else {
$source = "'" . $options . "'";
@@ -1335,8 +1335,8 @@ public static function filter($legend, $data, $action = null, $method = "POST",
}
// handle disabled fields
- if (substr($name, 0, 1) == '-') {
- $name = substr($name, 1);
+ if (substr(($name ?? ""), 0, 1) == '-') {
+ $name = substr(($name ?? ""), 1);
$readonly = " readonly='true' ";
}
diff --git a/system/modules/admin/actions/editlookup.php b/system/modules/admin/actions/editlookup.php
index 750221df9..75dd7f0f3 100755
--- a/system/modules/admin/actions/editlookup.php
+++ b/system/modules/admin/actions/editlookup.php
@@ -1,5 +1,7 @@
pathMatch("id", "type");
@@ -9,15 +11,43 @@ function editlookup_GET(Web &$w)
if ($lookup) {
$types = LookupService::getInstance($w)->getLookupTypes();
- $f = Html::form(array(
+ $w->out(HtmlBootstrap5::multiColForm([
+ 'Edit an Existing Entry' => [
+ [
+ (new Select([
+ 'id|name' => 'type',
+ 'selected_option' => $lookup->type,
+ 'label' => 'Type',
+ 'options' => $types,
+ ])),
+ ],
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'code',
+ 'label' => 'Code',
+ 'value' => $lookup->code,
+ ]))
+ ],
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'title',
+ 'label' => 'Title',
+ 'value' => $lookup->title,
+ ]))
+ ],
+ ],
+ ], $w->localUrl("/admin/editlookup/" . $lookup->id . "/" . $p['type']), "POST", " Update "));
+/*
+ $f = HtmlBootstrap5::multiColForm(array(
array("Edit an Existing Entry", "section"),
array("Type", "select", "type", $lookup->type, $types),
array("Key", "text", "code", $lookup->code),
array("Value", "text", "title", $lookup->title),
), $w->localUrl("/admin/editlookup/" . $lookup->id . "/" . $p['type']), "POST", " Update ");
- $w->setLayout(null);
+ //$w->setLayout(null);
$w->out($f);
+*/
} else {
$w->msg("No such Lookup Item?", "/admin/lookup/");
}
diff --git a/system/modules/admin/actions/editprinter.php b/system/modules/admin/actions/editprinter.php
index fdfa2d855..9f703bc43 100755
--- a/system/modules/admin/actions/editprinter.php
+++ b/system/modules/admin/actions/editprinter.php
@@ -16,7 +16,7 @@ function editprinter_GET(Web $w)
]
];
- $w->out(Html::multiColForm($form, "/admin/editprinter/{$p['id']}", "POST", "Save", null, null, null, "_self", true, Printer::$_validation));
+ $w->out(HtmlBootstrap5::multiColForm($form, "/admin/editprinter/{$p['id']}", "POST", "Save", null, null, null, "_self", true, Printer::$_validation));
}
function editprinter_POST(Web $w)
diff --git a/system/modules/admin/actions/groupadd.php b/system/modules/admin/actions/groupadd.php
index a5ace8923..020d1f5df 100755
--- a/system/modules/admin/actions/groupadd.php
+++ b/system/modules/admin/actions/groupadd.php
@@ -4,14 +4,25 @@
*
* @param
$w
*/
+
+use Html\Form\InputField\Text;
+
function groupadd_GET(Web $w)
{
- $template['New Group'] = array(array(array("Group Title: ","text","title")));
- $validation = ['title' => ['required']];
- $w->out(Html::multiColForm($template,"/admin/groupadd","POST","Save", null, null, null, "_self", true, $validation));
+ $w->setLayout('layout-bootstrap-5');
+
+ $w->out(HtmlBootstrap5::multiColForm([
+ 'New Group' => [
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'title',
+ 'label' => 'Group title',
+ 'required' => true,
+ ]))
+ ]
+ ]], "/admin/groupadd", "POST", "Save"));
- $w->setLayout(null);
}
function groupadd_POST(Web $w)
@@ -23,7 +34,7 @@ function groupadd_POST(Web $w)
$user->insert();
if (!empty($user->id)) {
- $w->msg("New group added!", "/admin/groups");
+ $w->msg("New group added", "/admin/groups");
} else {
$w->msg("Unable to create group.", "/admin/groups");
}
diff --git a/system/modules/admin/actions/groupdelete.php b/system/modules/admin/actions/groupdelete.php
index 908b3a665..6e55994f9 100755
--- a/system/modules/admin/actions/groupdelete.php
+++ b/system/modules/admin/actions/groupdelete.php
@@ -21,5 +21,5 @@ function groupdelete_GET(Web &$w)
$member->delete();
}
}
- $w->msg("Group is deleted!", "/admin/groups");
-}
\ No newline at end of file
+ $w->msg("Group deleted", "/admin/groups");
+}
diff --git a/system/modules/admin/actions/groupedit.php b/system/modules/admin/actions/groupedit.php
index 434ddf071..4eca5b020 100755
--- a/system/modules/admin/actions/groupedit.php
+++ b/system/modules/admin/actions/groupedit.php
@@ -4,17 +4,30 @@
*
* @param $w
*/
+
+use Html\Form\InputField\Text;
+
function groupedit_GET(Web $w)
{
+ $w->setLayout('layout-bootstrap-5');
+
$option = $w->pathMatch("group_id");
$user = AuthService::getInstance($w)->getUser($option['group_id']);
- $template['Edit Group'] = array(array(array("Group Title: ","text","title",$user->login)));
+ $w->out(HtmlBootstrap5::multiColForm([
+ 'Edit Group' => [
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'title',
+ 'label' => 'Group title',
+ 'value' => $user->login,
+ 'required' => true,
+ ]))
+ ]
+ ]
+ ], "/admin/groupedit/" . $option['group_id'], "POST", "Save"));
- $w->out(Html::multiColForm($template,"/admin/groupedit/".$option['group_id'],"POST","Save"));
-
- $w->setLayout(null);
}
function groupedit_POST(Web $w)
@@ -25,5 +38,5 @@ function groupedit_POST(Web $w)
$user->login = $_REQUEST['title'];
$user->update();
- $w->msg("Group info updated!", "/admin/groups");
-}
+ $w->msg("Group title updated", "/admin/groups");
+}
\ No newline at end of file
diff --git a/system/modules/admin/actions/groupmember.php b/system/modules/admin/actions/groupmember.php
index 8674a995f..98c885727 100755
--- a/system/modules/admin/actions/groupmember.php
+++ b/system/modules/admin/actions/groupmember.php
@@ -19,9 +19,9 @@ function groupmember_GET(Web $w)
$select[!empty($user->is_group)][$name] = array($name, $user->id);
}
}
-
- ksort($select[0]);
- ksort($select[1]);
+ // Sort ignoring case
+ ksort($select[0], SORT_STRING | SORT_FLAG_CASE);
+ ksort($select[1], SORT_STRING | SORT_FLAG_CASE);
$template['New Member'] = [[["Select Member: ", "select", "member_id", null, $select[0] + $select[1]]]];
if (AuthService::getInstance($w)->user()->is_admin) {
@@ -30,7 +30,7 @@ function groupmember_GET(Web $w)
$validation = ['member_id' => ['required']];
- $w->out(Html::multiColForm($template, "/admin/groupmember/" . $option['group_id'], "POST", "Save", null, null, null, "_self", true, $validation));
+ $w->out(HtmlBootstrap5::multiColForm($template, "/admin/groupmember/" . $option['group_id'], "POST", "Save", null, null, null, "_self", true, $validation));
$w->setLayout(null);
}
@@ -77,6 +77,6 @@ function groupmember_POST(Web $w)
if (!empty($exceptions)) {
$w->error(implode(", ", $exceptions) . " can not be added!", "/admin/moreInfo/" . $group_id);
} else {
- $w->msg("New members are added!", "/admin/moreInfo/" . $group_id);
+ $w->msg("New member added", "/admin/moreInfo/" . $group_id);
}
}
diff --git a/system/modules/admin/actions/groups.php b/system/modules/admin/actions/groups.php
index 3239c1228..575616ac6 100755
--- a/system/modules/admin/actions/groups.php
+++ b/system/modules/admin/actions/groups.php
@@ -1,52 +1,70 @@
$w
-*/
+ * Display a list of all groups which are not deleted
+ *
+ * @param $w
+ */
function groups_GET(Web &$w)
{
- AdminService::getInstance($w)->navigation($w,"Groups");
- $table = array(array("Title","Parent Groups","Operations"));
+ $w->setLayout('layout-bootstrap-5');
+
+ AdminService::getInstance($w)->navigation($w, "Groups");
+
+ $table = array(array("Title", "Parent Groups", "Actions"));
+ $table = array(array("Title", "Parent Groups", "Operations", "sort_key" => null));
$groups = AuthService::getInstance($w)->getGroups();
- if ($groups)
- {
- foreach ($groups as $group)
- {
+ if ($groups) {
+ foreach ($groups as $group) {
$ancestors = array();
-
- $line = array();
- $line[] = AuthService::getInstance($w)->user()->is_admin ? Html::box($w->localUrl("/admin/groupedit/".$group->id),"".$group->login."") : $group->login;
+ $line = array();
+ // Use box but set $button=false to remove button styling
+ //$line[] = AuthService::getInstance($w)->user()->is_admin ? HtmlBootstrap5::a("/admin/groupedit/" . $group->id, $group->login) : $group->login;
+ $line[] = AuthService::getInstance($w)->user()->is_admin ? HtmlBootstrap5::box("/admin/groupedit/" . $group->id, $group->login, false, false, null, null, 'isbox', null, " link-primary cursor-pointer ") : $group->login;
//if it is a sub group from other group;
$groupUsers = $group->isInGroups();
- if ($groupUsers)
- {
- foreach ($groupUsers as $groupUser)
- {
+ if ($groupUsers) {
+ foreach ($groupUsers as $groupUser) {
$ancestors[] = $groupUser->getGroup()->login;
}
}
- $line[] = count($ancestors) > 0 ? "".implode(", ", $ancestors)."
" : "";
+ $line[] = count($ancestors) > 0 ? "" . implode(", ", $ancestors) . "
" : "";
- $operations = Html::b("/admin/moreInfo/".$group->id,"More Info");
-
- if (AuthService::getInstance($w)->user()->is_admin)
- $operations .= Html::b("/admin/groupdelete/".$group->id,"Delete","Are you sure you want to delete this group?");
+ $buttonGroup = HtmlBootstrap5::b("/admin/moreInfo/" . $group->id, "Edit", null, "editbutton", false, 'btn-sm btn-secondary');
+ if (AuthService::getInstance($w)->user()->is_admin) {
+ $buttonGroup .= HtmlBootstrap5::b("/admin/groupdelete/" . $group->id, "Delete", "Are you sure you want to delete this group?", "deletebutton", false, "btn-sm btn-danger");
+ }
+ $operations = HtmlBootstrap5::buttonGroup($buttonGroup);
$line[] = $operations;
-
+
+ $line["sort_key"] = strtoupper($group->login);
+
$table[] = $line;
}
}
- if (AuthService::getInstance($w)->user()->is_admin)
- {
- $w->out(Html::box("/admin/groupadd", "New Group", true));
+ if (AuthService::getInstance($w)->user()->is_admin) {
+ //$w->out(HtmlBootstrap5::box("/admin/groupadd", "New Group", true, false, null, null, 'isbox', null, 'btn btn-sm btn-primary'));
+ $w->ctx("button", HtmlBootstrap5::box("/admin/groupadd", "New Group", true, false, null, null, 'isbox', null, 'btn btn-sm btn-primary'));
}
- $w->out(Html::table($table,null,"tablesorter",true));
+
+ // Order by sort key (group name in uppercase)
+ array_multisort(
+ array_column($table, "sort_key"),
+ SORT_ASC,
+ $table
+ );
+ // Remove sort column
+ for ($i = 0, $length = count($table); $i < $length; ++$i) {
+ unset($table[$i]["sort_key"]);
+ }
+
+ //$w->out(HtmlBootstrap5::table($table, null, "tablesorter", true));
+ $w->ctx("table", HtmlBootstrap5::table($table, null, "tablesorter", true));
}
diff --git a/system/modules/admin/actions/index.php b/system/modules/admin/actions/index.php
index 2730997dc..5d994e7db 100755
--- a/system/modules/admin/actions/index.php
+++ b/system/modules/admin/actions/index.php
@@ -2,6 +2,8 @@
function index_ALL(Web &$w)
{
+ $w->setLayout('layout-bootstrap-5');
+
AdminService::getInstance($w)->navigation($w, "Dashboard");
$w->ctx("currentUsers", AuditService::getInstance($w)->getLoggedInUsers());
}
diff --git a/system/modules/admin/actions/lookup.php b/system/modules/admin/actions/lookup.php
index 4637a8f47..e94e1b3fe 100755
--- a/system/modules/admin/actions/lookup.php
+++ b/system/modules/admin/actions/lookup.php
@@ -1,12 +1,17 @@
setLayout('layout-bootstrap-5');
+
AdminService::getInstance($w)->navigation($w, "Lookup");
$types = LookupService::getInstance($w)->getLookupTypes();
- $typelist = Html::select("type", $types, Request::string('type'));
- $w->ctx("typelist", $typelist);
+ $selectedtype = Request::string('type');
+ $w->ctx("selectedtype", $selectedtype);
// tab: Lookup List
$where = [];
@@ -23,37 +28,73 @@ function lookup_ALL(Web &$w)
$lookup = LookupService::getInstance($w)->getLookupsWhere($where);
- $line[] = ["Type", "Code", "Title", "Actions"];
+ $table = array(array("Type", "Code", "Title", "Actions", "sort_key" => null));
- if ($lookup) {
+ if (!empty($lookup)) {
foreach ($lookup as $look) {
- $line[] = [
- $look->type,
- $look->code,
- $look->title,
- Html::box($w->localUrl("/admin/editlookup/" . $look->id . "/" . urlencode(Request::string('type', ''))), " Edit ", true) .
- " " .
- Html::b($w->webroot() . "/admin/deletelookup/" . $look->id . "/" . urlencode(Request::string('type', '')), " Delete ", "Are you sure you wish to DELETE this Lookup item?")
- ];
+ $line = [];
+
+ $line[] = $look->type;
+ $line[] = $look->code;
+ $line[] = $look->title;
+ $line[] = HtmlBootstrap5::buttonGroup(
+ HtmlBootstrap5::box($w->localUrl("/admin/editlookup/" . $look->id . "/" . urlencode(Request::string('type', ''))), " Edit ", true, false, null, null, 'isbox', null, 'btn btn-sm btn-secondary') .
+ HtmlBootstrap5::b($w->webroot() . "/admin/deletelookup/" . $look->id . "/" . urlencode(Request::string('type', '')), " Delete ", "Are you sure you wish to DELETE this Lookup item?", "deletebutton", false, "btn-sm btn-danger")
+ );
+ $line['sort_key'] = strtoupper($look->type) . strtoupper($look->code) . strtoupper($look->title);
+
+ $table[] = $line;
+ }
+
+ // Order by sort key (group name in uppercase)
+ array_multisort(
+ array_column($table, "sort_key"),
+ SORT_ASC,
+ $table
+ );
+ // Remove sort column
+ for ($i = 0, $length = count($table); $i < $length; ++$i) {
+ unset($table[$i]["sort_key"]);
}
} else {
- $line[] = ["No Lookup items to list", null, null, null];
+ $table[] = ["No Lookup items to list", null, null, null, null];
}
// display list of items, if any
- $w->ctx("listitem", Html::table($line, null, "tablesorter", true));
+ $w->ctx("listitem", HtmlBootstrap5::table($table, null, "tablesorter", true));
// tab: new lookup item
$types = LookupService::getInstance($w)->getLookupTypes();
- $f = Html::form([
- ["Create a New Entry", "section"],
- ["Type", "select", "type", null, $types],
- ["or Add New Type", "text", "ntype"],
- ["Code", "text", "code"],
- ["Title", "text", "title"],
- ], $w->localUrl("/admin/newlookup/"), "POST", " Save");
-
- $w->ctx("newitem", $f);
+ $w->ctx('newitem', HtmlBootstrap5::multiColForm([
+ 'Create a New Entry' => [
+ [
+ (new Select([
+ 'id|name' => 'type',
+ 'selected_option' => null,
+ 'label' => 'Type',
+ 'options' => $types,
+ ])),
+ ],
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'ntype',
+ 'label' => 'or Add New Type',
+ ]))
+ ],
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'code',
+ 'label' => 'Code',
+ ]))
+ ],
+ [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'title',
+ 'label' => 'Title',
+ ]))
+ ],
+ ],
+ ], $w->localUrl("/admin/newlookup/"), "POST", "Save"));
}
diff --git a/system/modules/admin/actions/maintenance/index.php b/system/modules/admin/actions/maintenance/index.php
index a9e078dba..2485744a9 100644
--- a/system/modules/admin/actions/maintenance/index.php
+++ b/system/modules/admin/actions/maintenance/index.php
@@ -2,6 +2,8 @@
function index_GET(Web $w)
{
+ $w->setLayout('layout-bootstrap-5');
+
$w->ctx('title', 'Cmfive Maintenance');
// Get server name
diff --git a/system/modules/admin/actions/memberdelete.php b/system/modules/admin/actions/memberdelete.php
index e0974eba2..b4975f608 100755
--- a/system/modules/admin/actions/memberdelete.php
+++ b/system/modules/admin/actions/memberdelete.php
@@ -9,5 +9,5 @@ function memberdelete_GET(Web &$w)
{
$member->delete();
}
- $w->msg("Member is deleted!", "/admin/moreInfo/".$option['group_id']);
-}
\ No newline at end of file
+ $w->msg("Member deleted", "/admin/moreInfo/".$option['group_id']);
+}
diff --git a/system/modules/admin/actions/migration/create.php b/system/modules/admin/actions/migration/create.php
index b7a72284d..6d0bb4689 100644
--- a/system/modules/admin/actions/migration/create.php
+++ b/system/modules/admin/actions/migration/create.php
@@ -8,24 +8,60 @@ function create_GET(Web $w) {
}
$form = [
- "Enter the migration name (camel case)" => [
- [["Name", "text", "name"]]
+ 'Enter the migration name' => [
+ [
+ (
+ new \Html\Form\InputField(
+ [
+ "id|name" => "name",
+ 'label' => 'Name',
+ 'required' => true,
+ 'pattern' => '^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\s+\x80-\xff]*$',
+ 'title' => 'Must be a letter or underscore followed by letters, numbers, underscores, or spaces'
+ ]
+ )
+ )
+ ]
]
];
- $w->out(Html::multiColForm($form, "/admin-migration/create/" . $p['module'], "POST", "Save", null, null, null, "_self", true, Migration::$_validation));
+ $w->out(HtmlBootstrap5::multiColForm($form, "/admin-migration/create/" . $p['module'], "POST", "Save", null, null, null, "_self", true, Migration::$_validation));
}
function create_POST(Web $w) {
$p = $w->pathMatch("module");
if (empty($p['module']) || !in_array($p['module'], $w->modules())) {
- $w->error("Missing specified module or it doesn't exist", "/admin-migration");
+ $w->error("Missing specified module or it doesn't exist", "/admin-migration#individual");
}
$name = Request::string('name', 'migration');
-
+
+ // matches a letter/underscore followed by letters/underscores/numbers/spaces
+ $valid_string = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\s+\x80-\xff]*$/';
+
+ // matches any character that would cause the string to be invalid
+ $invalid_characters = '/(^[^a-zA-Z_\x80-\xff])|([^a-zA-Z0-9_\s+\x80-\xff])/';
+
+ if (!preg_match($valid_string, $name)) {
+
+ // wrap all invalid characters with an emphasis tag and apply the red squiggly underline class
+ $name = preg_replace_callback(
+ $invalid_characters,
+ function ($matches) {
+ return '' . $matches[0] . '';
+ },
+ $name
+ );
+
+ $w->error("Invalid migration name: " . $name, "/admin-migration#individual");
+ }
+
+ // remove whitespace, and ensure camel case
+ $name = implode("", array_map('ucfirst', preg_split('/\s+/', $name)));
+
+
$response = MigrationService::getInstance($w)->createMigration($p['module'], $name);
- $w->msg($response, "/admin-migration");
+ $w->msg($response, "/admin-migration#individual");
}
\ No newline at end of file
diff --git a/system/modules/admin/actions/migration/createseed.php b/system/modules/admin/actions/migration/createseed.php
index 65db2f98d..d400bb469 100644
--- a/system/modules/admin/actions/migration/createseed.php
+++ b/system/modules/admin/actions/migration/createseed.php
@@ -1,15 +1,41 @@
setLayout(null);
+ $w->setLayout('layout-bootstrap-5');
$w->out(
- Html::multiColForm([
- 'Create a seed' => [
- [["Module", "select", "module", null, $w->modules()]],
- [["Name", "text", "name"]]
- ]
- ], '/admin-migration/createseed')
+ HtmlBootstrap5::multiColForm(
+ [
+ 'Create a seed' => [
+ [
+ new Select(
+ [
+ "id|name" => "module",
+ 'label' => 'Module',
+ 'selected_option' => Request::string('default-selection') ?? null,
+ 'options' => $w->modules(),
+ 'required' => true
+ ]
+ )
+ ],
+ [
+ new \Html\Form\InputField(
+ [
+ "id|name" => "name",
+ 'label' => 'Name',
+ 'required' => true,
+
+ 'pattern' => '^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\s+\x80-\xff]*$',
+ 'title' => 'Must be a letter or underscore followed by letters, numbers, underscores, or spaces'
+ ]
+ )
+ ]
+ ]
+ ],
+ '/admin-migration/createseed'
+ )
);
}
@@ -22,6 +48,28 @@ function createseed_POST(Web $w) {
$w->error('Missing data', '/admin-migration#seed');
}
+ // matches a letter/underscore followed by letters/underscores/numbers/spaces
+ $valid_string = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\s+\x80-\xff]*$/';
+
+ // matches any character that would cause the string to be invalid
+ $invalid_characters = '/(^[^a-zA-Z_\x80-\xff])|([^a-zA-Z0-9_\s+\x80-\xff])/';
+
+ if (!preg_match($valid_string, $name)) {
+ // wrap all invalid characters with an emphasis tag and apply the red squiggly underline class
+ $name = preg_replace_callback(
+ $invalid_characters,
+ function ($matches) {
+ return '' . $matches[0] . '';
+ },
+ $name
+ );
+
+ $w->error('Invalid migration name: ' . $name, '/admin-migration#individual');
+ }
+
+ // remove whitespace, and ensure camel case
+ $name = implode('', array_map('ucfirst', preg_split('/\s+/', $name)));
+
$response = MigrationService::getInstance($w)->createMigrationSeed($module, $name);
if ($response) {
diff --git a/system/modules/admin/actions/migration/index.php b/system/modules/admin/actions/migration/index.php
index 86fadf676..c45b63005 100644
--- a/system/modules/admin/actions/migration/index.php
+++ b/system/modules/admin/actions/migration/index.php
@@ -2,6 +2,8 @@
function index_GET(Web $w)
{
+ $w->setLayout('layout-bootstrap-5');
+
$available = MigrationService::getInstance($w)->getAvailableMigrations('all');
$installed = MigrationService::getInstance($w)->getInstalledMigrations('all');
$seeds = MigrationService::getInstance($w)->getSeedMigrations();
diff --git a/system/modules/admin/actions/moreInfo.php b/system/modules/admin/actions/moreInfo.php
index ae6a4cc61..4655e1c19 100755
--- a/system/modules/admin/actions/moreInfo.php
+++ b/system/modules/admin/actions/moreInfo.php
@@ -1,49 +1,57 @@
$w
-*/
+ * Display member and permission infomation
+ *
+ * @param $w
+ */
function moreInfo_GET(Web &$w)
{
+ $w->setLayout('layout-bootstrap-5');
+
$option = $w->pathMatch("group_id");
AdminService::getInstance($w)->navigation($w, AuthService::getInstance($w)->getUser($option['group_id'])->login);
- if (AuthService::getInstance($w)->user()->is_admin || AuthService::getInstance($w)->getRoleForLoginUser($option['group_id'], AuthService::getInstance($w)->user()->id) == "owner")
- {
- $w->ctx("addMember", Html::box("/admin/groupmember/".$option['group_id'],"New Member",true));
+ if (AuthService::getInstance($w)->user()->is_admin || AuthService::getInstance($w)->getRoleForLoginUser($option['group_id'], AuthService::getInstance($w)->user()->id) == "owner") {
+ $w->ctx("addMember", HtmlBootstrap5::box("/admin/groupmember/" . $option['group_id'], "New Member", true, false, null, null, 'isbox', null, "btn btn-sm btn-primary"));
}
- $w->ctx("editPermission", Html::b("/admin/permissionedit/".$option['group_id'],"Edit Permissions"));
-
+ $w->ctx("editPermission", HtmlBootstrap5::b("/admin/permissionedit/" . $option['group_id'], "Edit Permissions", null, null, false, "btn btn-sm btn-primary"));
//fill in member table;
- $table = array(array("Name","Role","Operations"));
+ $table = array(array("Name", "Role", "Operations", "sort_key" => null));
$groupMembers = AuthService::getInstance($w)->getGroupMembers($option['group_id']);
-
- if ($groupMembers)
- {
- foreach ($groupMembers as $groupMember)
- {
+
+ if ($groupMembers) {
+ foreach ($groupMembers as $groupMember) {
$line = array();
-
- $style = $groupMember->role == "owner" ? "" : "
";
-
+
+ $style = $groupMember->role == "owner" ? "
" : "
";
+
$name = $groupMember->getUser()->is_group == 1 ? $groupMember->getUser()->login : $groupMember->getUser()->getContact()->getFullName();
-
- $line[] = $style.$name."
";
- $line[] = $style.$groupMember->role."
";
-
- if (AuthService::getInstance($w)->user()->is_admin || AuthService::getInstance($w)->getRoleForLoginUser($option['group_id'], AuthService::getInstance($w)->user()->id) == "owner")
- {
- $line[] = Html::a("/admin/memberdelete/".$option['group_id']."/".$groupMember->id,"Delete",null,null,"Are you sure you want to delete this member?");
- }
- else
- {
+
+ $line[] = $style . $name . "
";
+ $line[] = $style . $groupMember->role . "
";
+
+ if (AuthService::getInstance($w)->user()->is_admin || AuthService::getInstance($w)->getRoleForLoginUser($option['group_id'], AuthService::getInstance($w)->user()->id) == "owner") {
+ $line[] = HtmlBootstrap5::b("/admin/memberdelete/" . $option['group_id'] . "/" . $groupMember->id, "Delete", "Are you sure you want to delete this group?", "deletebutton", false, "btn-sm btn-danger");
+ } else {
$line[] = null;
}
+ $line["sort_key"] = strtoupper($name);
$table[] = $line;
}
}
- $w->ctx("memberList", Html::table($table,null,"tablesorter",true));
+ // Order by sort key (name/group in uppercase)
+ array_multisort(
+ array_column($table, "sort_key"),
+ SORT_ASC,
+ $table
+ );
+ // Remove sort column
+ for ($i = 0, $length = count($table); $i < $length; ++$i) {
+ unset($table[$i]["sort_key"]);
+ }
+
+ $w->ctx("memberList", HtmlBootstrap5::table($table, null, "tablesorter", true));
}
diff --git a/system/modules/admin/actions/newlookup.php b/system/modules/admin/actions/newlookup.php
index a08c33930..44b2bb506 100755
--- a/system/modules/admin/actions/newlookup.php
+++ b/system/modules/admin/actions/newlookup.php
@@ -11,7 +11,7 @@ function newlookup_POST(Web &$w) {
$err .= "Please enter a KEY
";
if ($_REQUEST['title'] == "")
$err .= "Please enter a VALUE
";
- if (LookupService::getInstance($w)->getLookupByTypeAndCode($_REQUEST['type'],$_REQUEST['code']))
+ if (LookupService::getInstance($w)->getLookupByTypeAndCodeV2($_REQUEST['type'], $_REQUEST['code']))
$err .= "Type and Key combination already exists";
if ($err != "") {
diff --git a/system/modules/admin/actions/permissionedit.php b/system/modules/admin/actions/permissionedit.php
index 7a9902a4a..5f6537c50 100755
--- a/system/modules/admin/actions/permissionedit.php
+++ b/system/modules/admin/actions/permissionedit.php
@@ -1,6 +1,9 @@
setLayout('layout-bootstrap-5');
+
$option = $w->pathMatch("group_id");
$user = AuthService::getInstance($w)->getUser($option['group_id']);
@@ -50,7 +53,7 @@ function permissionedit_GET(Web $w) {
}
$action = AuthService::getInstance($w)->user()->is_admin ? "/admin/permissionedit/" . $option['group_id'] : null;
- $w->ctx("permission", Html::multiColForm($permission, $action));
+ $w->ctx("permission", HtmlBootstrap5::multiColForm($permission, $action));
$w->ctx("groupRoles", json_encode($groupRoles));
}
@@ -78,5 +81,5 @@ function permissionedit_POST(Web &$w) {
}
$returnPath = $user->is_group == 1 ? "/admin/moreInfo/" . $option['group_id'] : "/admin/users";
- $w->msg("Permissions are updated!", $returnPath);
+ $w->msg("Permissions updated", $returnPath);
}
diff --git a/system/modules/admin/actions/printers.php b/system/modules/admin/actions/printers.php
index a38fa1d95..b1ff2e10e 100755
--- a/system/modules/admin/actions/printers.php
+++ b/system/modules/admin/actions/printers.php
@@ -1,6 +1,8 @@
setLayout('layout-bootstrap-5');
+
$printers = PrinterService::getInstance($w)->getPrinters();
$table_data = array();
$table_header = array("Name", "Server", "Port", "Actions");
@@ -8,8 +10,8 @@ function printers_GET(Web $w) {
foreach($printers as $printer) {
$table_data[] = array(
$printer->name, $printer->server, $printer->port,
- Html::box("/admin/editprinter/{$printer->id}", "Edit", true) .
- Html::b("/admin/deleteprinter/{$printer->id}", "Delete", "Are you sure you want to delete this printer?")
+ HtmlBootstrap5::box("/admin/editprinter/{$printer->id}", "Edit", true, false, null, null, 'isbox', null, 'btn btn-sm btn-primary') .
+ HtmlBootstrap5::b("/admin/deleteprinter/{$printer->id}", "Delete", "Are you sure you want to delete this printer?", "deletebutton", false, "btn-sm btn-danger")
);
}
}
diff --git a/system/modules/admin/actions/printqueue.php b/system/modules/admin/actions/printqueue.php
index 9b28ec755..079963d0e 100755
--- a/system/modules/admin/actions/printqueue.php
+++ b/system/modules/admin/actions/printqueue.php
@@ -1,6 +1,8 @@
setLayout('layout-bootstrap-5');
+
$print_folder = FILE_ROOT . "print";
$path = realpath($print_folder);
@@ -25,14 +27,14 @@ function printqueue_GET(Web $w) {
}
$table_data[] = array(
- Html::a("/uploads/print/" . $filename, $filename),
+ HtmlBootstrap5::a("/uploads/print/" . $filename, $filename),
// Function below in functions.php
humanReadableBytes($object->getSize()),
date("H:i d/m/Y", filectime($name)),
- Html::box("/admin/printfile?filename=" . urlencode($name), "Print", true) . " " .
- Html::b("/admin/deleteprintfile?filename=" . urlencode($name), "Delete", "Are you sure you want to remove this file? (This is irreversible)")
+ HtmlBootstrap5::box("/admin/printfile?filename=" . urlencode($name), "Print", true, false, null, null, 'isbox', null, 'btn btn-sm btn-primary') . " " .
+ HtmlBootstrap5::b("/admin/deleteprintfile?filename=" . urlencode($name), "Delete", "Are you sure you want to remove this file? (This is irreversible)", "deletebutton", false, "btn-sm btn-danger")
);
}
- $w->out(Html::table($table_data, null, "tablesorter", $table_header));
+ $w->out(HtmlBootstrap5::table($table_data, null, "tablesorter", $table_header));
}
\ No newline at end of file
diff --git a/system/modules/admin/actions/templates/edit.php b/system/modules/admin/actions/templates/edit.php
index 6d85e49c3..a1988151a 100755
--- a/system/modules/admin/actions/templates/edit.php
+++ b/system/modules/admin/actions/templates/edit.php
@@ -5,6 +5,8 @@
function edit_GET(Web $w)
{
+ $w->setLayout('layout-bootstrap-5');
+
AdminService::getInstance($w)->navigation($w, "Templates");
$p = $w->pathMatch("id");
@@ -14,41 +16,88 @@ function edit_GET(Web $w)
$newForm = [];
$newForm["Template Details"] = [
[
- ["Title", "text", "title", $t->title],
- ["Active", "checkbox", "is_active", $t->is_active]
+ (new \Html\Form\InputField\Text([
+ "id|name" => "title",
+ "label" => "Title",
+ "required" => true,
+ "value" => $t->title
+ ])), // ["Title", "text", "title", $t->title],
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "is_active",
+ 'label' => 'Active',
+ "class" => "",
+ "checked" => $t->is_active ? $t->is_active : null
+ ]))// ->setAttribute("is_active", $t->is_active) //["Active", "checkbox", "is_active", $t->is_active]
],
[
- ["Module", "text", "module", $t->module],
- ["Category", "text", "category", $t->category]
+ (new \Html\Form\InputField\Text([
+ "id|name" => "module",
+ "label" => "Module",
+ "required" => true,
+ "value" => $t->module
+ ])), //["Module", "text", "module", $t->module],
+ (new \Html\Form\InputField\Text([
+ "id|name" => "category",
+ "label" => "Category",
+ "required" => true,
+ "value" => $t->category
+ ])) //["Category", "text", "category", $t->category]
]
];
$newForm['Description'] = [
[
- ["", "textarea", "description", $t->description]
+ (new \Html\Cmfive\QuillEditor([
+ "id|name" => "description",
+ "value" => $t->description,
+ "label" => "Description",
+ ]))->setOptions(["placeholder" => "Please provide a brief description of the template"]) //["", "textarea", "description", $t->description]
],
];
- $w->ctx("editdetailsform", Html::multiColForm($newForm, $w->localUrl('/admin-templates/edit/' . $t->id)));
+ $w->ctx("editdetailsform", HtmlBootstrap5::multiColForm($newForm, $w->localUrl('/admin-templates/edit/' . $t->id)));
$newForm = [];
$newForm["Template Title"] = [
- [["", "textarea", "template_title", $t->template_title, 100, 1, false]]
+ [
+ (new \Html\Form\InputField\Text([
+ "id|name" => "template_title",
+ "value" => $t->template_title,
+ "maxlength" => 100
+ ])) //["", "textarea", "template_title", $t->template_title, 100, 1, false]
+ ]
];
$newForm["Template Body"] = [
- [["", "textarea", "template_body", $t->template_body, 60, 100, "codemirror"]]
+ [
+ (new \Html\Cmfive\CodeMirrorEditor([
+ "id|name" => "template_body",
+ "value" => $t->template_body,
+ ]))//->addToConfig(['extensions' => ['basicSetup'], 'parent' => 'template_body']) //["", "textarea", "template_body", $t->template_body, 60, 100, "codemirror"]
+ ]
];
- $w->ctx("templateform", Html::multiColForm($newForm, $w->localUrl('/admin-templates/edit/' . $t->id)));
+ $w->ctx("templateform", HtmlBootstrap5::multiColForm($newForm, $w->localUrl('/admin-templates/edit/' . $t->id)));
$newForm = [];
$newForm["Title Data"] = [
- [["", "textarea", "test_title_json", $t->test_title_json, 100, 5, false]]
+ [
+ (new \Html\Form\InputField\Text([
+ "id|name" => "test_title_json",
+ "value" => $t->test_title_json,
+ "maxlength" => 500
+ ])) //["", "textarea", "test_title_json", $t->test_title_json, 100, 5, false]]
+ ]
];
$newForm["Body Data"] = [
- [["", "textarea", "test_body_json", $t->test_body_json, 100, 20, false]]
+ [
+ (new \Html\Cmfive\QuillEditor([
+ "id|name" => "test_body_json",
+ "value" => $t->test_body_json,
+ "maxlength" => 2000
+ ])) // ["", "textarea", "test_body_json", $t->test_body_json, 100, 20, false]]
+ ]
];
- $w->ctx("testdataform", Html::multiColForm($newForm, $w->localUrl('/admin-templates/edit/' . $t->id)));
+ $w->ctx("testdataform", HtmlBootstrap5::multiColForm($newForm, $w->localUrl('/admin-templates/edit/' . $t->id)));
$w->ctx("id", $p['id']);
}
@@ -60,9 +109,9 @@ function edit_POST(Web $w)
// Set is active if saving is originating from the first page
if (isset($_POST["title"]) && isset($_POST["module"]) && isset($_POST["category"])) {
- $t->is_active = intval(Request::int("is_active"));
+ $t->is_active = !empty($_REQUEST['is_active']) ? $_REQUEST['is_active'] : 0;
}
$t->insertOrUpdate();
- $w->msg("Template saved", "/admin-templates/edit/" . $t->id);
+ $w->msg("Template saved", "/admin-templates/edit/" . $t->id . "#tab-1");
}
diff --git a/system/modules/admin/actions/templates/index.php b/system/modules/admin/actions/templates/index.php
index 95e71bf9a..54ad84d2a 100755
--- a/system/modules/admin/actions/templates/index.php
+++ b/system/modules/admin/actions/templates/index.php
@@ -19,11 +19,11 @@ function index_GET($w)
$t->title, $t->module, $t->category,
[$t->is_active ? "Active" : "Inactive", true],
// [Date("H:i d-m-Y", $t->dt_created), true],
- // [Date("H:i d-m-Y", $t->dt_modified), true],
- Html::b("/admin-templates/edit/" . $t->id, "Edit", false),
+ // [Date("H:i d-m-Y", $t->dt_modified), true],
+ HtmlBootstrap5::b("/admin-templates/edit/" . $t->id, "Edit", null, null, false, "btn-sm btn-secondary"),
];
}
}
- $w->ctx("templates_table", Html::table($table_data, null, "tablesorter", $table_header));
+ $w->ctx("templates_table", HtmlBootstrap5::table($table_data, null, "tablesorter", $table_header));
}
diff --git a/system/modules/admin/actions/user/edit.php b/system/modules/admin/actions/user/edit.php
index b8cf243f9..1b6d3c20f 100644
--- a/system/modules/admin/actions/user/edit.php
+++ b/system/modules/admin/actions/user/edit.php
@@ -1,7 +1,12 @@
setLayout('layout-bootstrap-5');
+
VueComponentRegister::registerComponent("autocomplete", new VueComponent("autocomplete", "/system/templates/vue-components/form/elements/autocomplete.vue.js", "/system/templates/vue-components/form/elements/autocomplete.vue.css"));
$redirect_url = "/admin/users";
@@ -16,6 +21,8 @@ function edit_GET(Web $w)
$w->error("Unable to find User", $redirect_url);
}
+ $availableLocales = $w->getAvailableLanguages();
+
$contact = $user->getContact();
if (empty($contact)) {
$w->error("Unable to find User", $redirect_url);
@@ -34,20 +41,9 @@ function edit_GET(Web $w)
}
}
- $titles_array = [];
-
- foreach (AuthService::getInstance($w)->getTitles() as $title) {
- $titles_array[] = [
- "id" => $title->id,
- "name" => $title->title,
- ];
- }
-
$user_details = [
"id" => $user->id,
"account" => [
- "title" => $contact->getTitle(),
- "titles" => $titles_array,
"firstname" => $contact->firstname,
"lastname" => $contact->lastname,
"othername" => $contact->othername,
@@ -57,6 +53,7 @@ function edit_GET(Web $w)
"priv_mobile" => $contact->priv_mobile,
"fax" => $contact->fax,
"email" => $contact->email,
+
],
"security" => [
"login" => $user->login,
@@ -72,4 +69,140 @@ function edit_GET(Web $w)
];
$w->ctx("user", $user_details);
+
+ $w->ctx('userDetails', HtmlBootstrap5::multiColForm([
+ 'User Details' => [
+ [
+ (new \Html\Form\InputField([
+ "id|name" => "login",
+ 'label' => 'Login',
+ "required" => true,
+ 'value' => $user->login,
+ ])),
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "admin",
+ 'label' => 'Admin',
+ "class" => "",
+ ]))->setAttribute("v-model", "user.security.is_admin"),
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "active",
+ 'label' => 'Active',
+ "class" => "",
+ ]))->setAttribute("v-model", "user.security.is_active"),
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "external",
+ 'label' => 'External',
+ "class" => "",
+ ]))->setAttribute("v-model", "user.security.is_external"),
+ (new Select([
+ "id|name" => "language",
+ 'label' => 'Language',
+ 'selected_option' => $user->language,
+ 'options' => $availableLocales,
+ ])),
+ ],
+ ],
+ 'Contact Details' => [
+ [
+ (new \Html\Form\InputField([
+ "id|name" => "firstname",
+ 'label' => 'First Name',
+ 'required' => true,
+ ]))->setAttribute("v-model", "user.account.firstname"),
+ (new \Html\Form\InputField([
+ "id|name" => "lastname",
+ 'label' => 'Last Name',
+ 'required' => true,
+ ]))->setAttribute("v-model", "user.account.lastname"),
+ ],
+ [
+ (new SelectWithOther([
+ "id|name" => "title_lookup_id",
+ 'label' => 'Title',
+ 'selected_option' => !empty($contact->title_lookup_id) ? LookupService::getInstance($w)->getLookup($contact->title_lookup_id)->code : null,
+ 'options' => LookupService::getInstance($w)->getLookupByType("title"),
+ 'other_field' => new \Html\Form\InputField([
+ 'id|name' => 'title_other',
+ 'placeholder' => 'Other Title'
+ ]),
+ ])),
+ (new \Html\Form\InputField([
+ "id|name" => "othername",
+ 'label' => 'Other Name',
+ ]))->setAttribute("v-model", "user.account.othername")
+ ],
+ [
+ (new \Html\Form\InputField\Tel([
+ "id|name" => "homephone",
+ 'label' => 'Home Phone',
+ ]))->setAttribute("v-model", "user.account.homephone"),
+ (new \Html\Form\InputField\Tel([
+ "id|name" => "workphone",
+ 'label' => 'Work Phone',
+ ]))->setAttribute("v-model", "user.account.workphone"),
+ (new \Html\Form\InputField\Tel([
+ "id|name" => "mobile",
+ 'label' => 'Mobile',
+ ]))->setAttribute("v-model", "user.account.mobile"),
+ ],
+ [
+ (new \Html\Form\InputField\Tel([
+ "id|name" => "priv_mobile",
+ 'label' => 'Private Mobile',
+ ]))->setAttribute("v-model", "user.account.priv_mobile"),
+ (new \Html\Form\InputField([
+ "id|name" => "fax",
+ 'label' => 'Fax',
+ ]))->setAttribute("v-model", "user.account.fax"),
+ (new \Html\Form\InputField\Email([
+ "id|name" => "email",
+ 'label' => 'Email',
+ ]))->setAttribute("v-model", "user.account.email")
+ ]
+ ],
+ ], '/admin-user/edit/' . $user->id));
+
}
+
+function edit_POST(Web $w): void
+{
+ $redirect_url = '/admin/users/';
+
+ list($user_id) = $w->pathMatch("id");
+ if (empty($user_id)) {
+ $w->error("Unable to find User", $redirect_url);
+ }
+
+ $user = AuthService::getInstance($w)->getUser($user_id);
+ if (empty($user)) {
+ $w->error("Unable to find User", $redirect_url);
+ }
+
+ $user->fill($_POST);
+ $user->is_admin = !empty($_POST['admin']) ? $_POST['admin'] : 0;
+ $user->is_active = !empty($_POST['active']) ? $_POST['active'] : 0;
+ $user->is_external = isset($_POST['external']) ? 1 : 0;
+
+ if (!$user->insertOrUpdate()) {
+ $w->error("Failed to update User details", $redirect_url);
+ }
+
+ $contact = $user->getContact();
+ if (empty($contact)) {
+ $w->error("Unable to find user", $redirect_url);
+ }
+
+ $contact->fill($_POST);
+ if ($_POST['title_lookup_id'] === "other") {
+ $contact->setTitle($_POST['title_other']);
+ } else {
+ $contact->setTitle($_POST['title_lookup_id']);
+ }
+
+ if (!$contact->insertOrUpdate(true)) { // need true to be able to update the title if no title selected now when one had been selected prior
+ $w->error("Failed to update contact details", $redirect_url);
+ }
+
+ $w->msg("User details updated", $redirect_url);
+
+}
\ No newline at end of file
diff --git a/system/modules/admin/actions/user/remove.php b/system/modules/admin/actions/user/remove.php
index 6a486f3d1..fd35aaffd 100644
--- a/system/modules/admin/actions/user/remove.php
+++ b/system/modules/admin/actions/user/remove.php
@@ -1,6 +1,8 @@
setLayout('layout-bootstrap-5');
list($user_id) = $w->pathMatch();
diff --git a/system/modules/admin/actions/useradd.php b/system/modules/admin/actions/useradd.php
index 0b2b2d751..39744d9d7 100755
--- a/system/modules/admin/actions/useradd.php
+++ b/system/modules/admin/actions/useradd.php
@@ -1,6 +1,8 @@
pathMatch("box");
- $w->setLayout("layout-2021");
+ //$w->setLayout("layout-2021");
+ $w->setLayout('layout-bootstrap-5');
$availableLocales = $w->getAvailableLanguages();
@@ -36,11 +39,31 @@ function useradd_GET(Web $w)
}
$form['User Details'][] = [
- ["Login","text","login"],
- ["Admin","checkbox","is_admin"],
- ["Active","checkbox","is_active"],
- ["External", "checkbox", "is_external"],
- ["Language", "select", "language", null, $availableLocales],
+ (new \Html\Form\InputField([
+ "id|name" => "login",
+ 'label' => 'Login',
+ "required" => true,
+ ])),
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "is_admin",
+ 'label' => 'Admin',
+ "class" => "",
+ ])),
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "is_active",
+ 'label' => 'Active',
+ "class" => "",
+ ])),
+ (new \Html\Form\InputField\Checkbox([
+ "id|name" => "is_external",
+ 'label' => 'External',
+ "class" => "",
+ ])),
+ (new Select([
+ "id|name" => "language",
+ 'label' => 'Language',
+ 'options' => $availableLocales,
+ ])),
];
$form['User Details'][] = [
@@ -49,26 +72,35 @@ function useradd_GET(Web $w)
];
$form['Contact Details'][] = [
- ["First Name", "text", "firstname"],
- ["Last Name", "text", "lastname"],
+ (new \Html\Form\InputField([
+ "id|name" => "firstname",
+ 'label' => 'First Name',
+ "required" => true,
+ ])),
+ (new \Html\Form\InputField([
+ "id|name" => "lastname",
+ 'label' => 'Last Name',
+ "required" => true,
+ ])),
];
$form['Contact Details'][] = [
- ["Title", "autocomplete", "title", null, LookupService::getInstance($w)->getLookupByType("title")],
- ["Email", "text", "email"],
+ (new SelectWithOther([
+ "id|name" => "title_lookup_id",
+ 'label' => 'Title',
+ 'options' => LookupService::getInstance($w)->getLookupByType("title"),
+ 'other_field' => new \Html\Form\InputField([
+ 'id|name' => 'title_other',
+ 'placeholder' => 'Other Title'
+ ]),
+ ])),
+ (new \Html\Form\InputField\Email([
+ "id|name" => "email",
+ 'label' => 'Email',
+ ]))
];
-
- $roles = AuthService::getInstance($w)->getAllRoles();
- $roles = array_chunk($roles, 4);
- foreach ($roles as $r) {
- $row = [];
- foreach ($r as $rf) {
- $row[] = [$rf, "checkbox", "check_" . $rf];
- }
- $form['User Roles'][] = $row;
- }
- $w->out(Html::multiColForm($form, $w->localUrl("/admin/useradd"), "POST", "Save", null, null, null, "_self", true, array_merge(User::$_validation, ['password' => ['required'], 'password2' => ['required']])));
+ $w->out(HtmlBootstrap5::multiColForm($form, $w->localUrl("/admin/useradd"), "POST", "Save", null, null, null, "_self", true, array_merge(User::$_validation, ['password' => ['required'], 'password2' => ['required']])));
}
/**
@@ -95,7 +127,11 @@ function useradd_POST(Web &$w)
$contact->fill($_REQUEST);
$contact->dt_created = time();
$contact->private_to_user_id = null;
- $contact->setTitle($_REQUEST['acp_title']);
+ if ($_REQUEST['title_lookup_id'] === "other") {
+ $contact->setTitle($_REQUEST['title_other']);
+ } else {
+ $contact->setTitle($_REQUEST['title_lookup_id']);
+ }
$contact->insert();
// now saving the user
@@ -116,9 +152,7 @@ function useradd_POST(Web &$w)
$roles = AuthService::getInstance($w)->getAllRoles();
foreach ($roles as $r) {
if (!empty($_REQUEST["check_" . $r])) {
- if ($_REQUEST["check_" . $r] == 1) {
- $user->addRole($r);
- }
+ $user->addRole($r);
}
}
$w->callHook("admin", "account_changed", $user);
diff --git a/system/modules/admin/actions/users.php b/system/modules/admin/actions/users.php
index a85f9e5c4..be25fc717 100755
--- a/system/modules/admin/actions/users.php
+++ b/system/modules/admin/actions/users.php
@@ -5,21 +5,148 @@ function users_GET(Web $w)
$w->setLayout("layout-bootstrap-5");
AdminService::getInstance($w)->navigation($w, "Users");
+ //if filter applied unset the page number
+ if (array_key_exists("admin/user__filter-login", $_REQUEST) ||
+ array_key_exists("admin/user__filter-name", $_REQUEST) ||
+ array_key_exists("admin/user__filter-email", $_REQUEST)
+ ) {
+ $filter_applied = true;
+ $w->sessionUnset("cmfive-internal-users__page-number");
+ $w->sessionUnset("cmfive-external-users__page-number");
+ } else {
+ $filter_applied = false;
+ }
+
$internal_page_number = $w->sessionOrRequest('cmfive-internal-users__page-number', 1);
+ $external_page_number = $w->sessionOrRequest('cmfive-external-users__page-number', 1);
+
+ // Get filter parameters
+ $login = $w->sessionOrRequest("admin/user__filter-login");
+ $name = $w->sessionOrRequest("admin/user__filter-name");
+ $email = $w->sessionOrRequest("admin/user__filter-email");
+ $reset = Request::string("filter_reset_users_filter");
+
+ $filter_url = "";
+
+ if (!empty($reset)) {
+ $login = null;
+ $w->sessionUnset("admin/user__filter-login");
+ $name = null;
+ $w->sessionUnset("admin/user__filter-name");
+ $email = null;
+ $w->sessionUnset("admin/user__filter-email");
+ $filter_applied = false;
+ }
+
+ if ($filter_applied === true) {
+ // Get the User.ids of Internal Users that match the search criteria
+ $filtered_int_user_ids = [];
+ $users = AdminService::getInstance($w)->getUsers(["user.is_external" => 0, "user.is_deleted" => 0, "user.is_group" => 0]);
+ if (!empty($users)) {
+ $i = 0;
+ foreach ($users as $user) {
+ $contact = AdminService::getInstance($w)->getObject("Contact", $user->contact_id);
+ if (!empty($contact) &&
+ str_contains(strtoupper($user->login), strtoupper($login)) &&
+ str_contains(strtoupper($contact->firstname . $contact->lastname), strtoupper($name)) &&
+ str_contains(strtoupper($contact->email), strtoupper($email))) {
+ $filtered_int_user_ids[$i++] = $user->id;
+ }
+ }
+ }
+
+ // Get the User.ids of External Users that match the search criteria
+ $filtered_ext_user_ids = [];
+ $users = AdminService::getInstance($w)->getUsers(["user.is_external" => 1, "user.is_deleted" => 0, "user.is_group" => 0]);
+ if (!empty($users)) {
+ $i = 0;
+ foreach ($users as $user) {
+ $contact = AdminService::getInstance($w)->getObject("Contact", $user->contact_id);
+ if (!empty($contact) &&
+ str_contains(strtoupper($user->login), strtoupper($login)) &&
+ str_contains(strtoupper($contact->firstname . $contact->lastname), strtoupper($name)) &&
+ str_contains(strtoupper($contact->email), strtoupper($email))) {
+ $filtered_ext_user_ids[$i++] = $user->id;
+ }
+ }
+ }
+
+ // Set up the filtered part of the url
+ if (!(empty($login) && empty($name) && empty($email))) {
+ $filter_url = "?";
+ if (!empty($login)) {
+ $filter_url .= "admin%2Fuser__filter-login=" . $login;
+ if (!(empty($name) && empty($email))) {
+ $filter_url .= "&";
+ }
+ }
+ if (!empty($name)) {
+ $filter_url .= "admin%2Fuser__filter-name=" . $name;
+ if (!(empty($email))) {
+ $filter_url .= "&";
+ }
+ }
+ if (!empty($email)) {
+ $filter_url .= "admin%2Fuser__filter-email=" . $email;
+ }
+ }
+ }
+
+ // Set up base urls
+ $internal_base_url = "/admin/users" . $filter_url;
+ $external_base_url = $internal_base_url . "#external";
+
+ $filterData = [
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'admin/user__filter-login',
+ 'value' => $login,
+ 'label' => 'Login',
+ ])),
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'admin/user__filter-name',
+ 'value' => $name,
+ 'label' => 'Name',
+ ])),
+ (new \Html\Form\InputField\Text([
+ 'id|name' => 'admin/user__filter-email',
+ 'value' => $email,
+ 'label' => 'Email',
+ ]))
+ ];
+
$internal_page_size = $w->sessionOrRequest('cmfive-internal-users__page-size', 50);
$internal_sort = $w->sessionOrRequest('cmfive-internal-users__sort', 'login');
$internal_sort_direction = $w->sessionOrRequest('cmfive-internal-users__sort-direction', 'asc');
- $external_page_number = $w->sessionOrRequest('cmfive-external-users__page-number', 1);
$external_page_size = $w->sessionOrRequest('cmfive-external-users__page-size', 50);
$external_sort = $w->sessionOrRequest('cmfive-external-users__sort', 'login');
$external_sort_direction = $w->sessionOrRequest('cmfive-external-users__sort-direction', 'asc');
- $internal_users = AdminService::getInstance($w)->getUsers(["user.is_external" => 0, "user.is_deleted" => 0, "user.is_group" => 0], $internal_page_number, $internal_page_size, $internal_sort, $internal_sort_direction);
- $internal_user_count = AdminService::getInstance($w)->countUsers(["is_external" => 0, "is_deleted" => 0, "is_group" => 0]);
+ if ($filter_applied === false) {
+ $internal_users = AdminService::getInstance($w)->getUsers(["user.is_external" => 0, "user.is_deleted" => 0, "user.is_group" => 0], $internal_page_number, $internal_page_size, $internal_sort, $internal_sort_direction);
+ $internal_user_count = AdminService::getInstance($w)->countUsers(["is_external" => 0, "is_deleted" => 0, "is_group" => 0]);
+ } elseif (!empty($filtered_int_user_ids)) {
+ $internal_users = AdminService::getInstance($w)->getUsers(["user.id" => $filtered_int_user_ids], $internal_page_number, $internal_page_size, $internal_sort, $internal_sort_direction);
+ $internal_user_count = AdminService::getInstance($w)->countUsers(["user.id" => $filtered_int_user_ids]);
+ } else {
+ $internal_users = null;
+ $internal_user_count = 0;
+ $internal_page_number = 1;
+ $w->sessionUnset("cmfive-internal-users__page-number");
+ }
- $external_users = AdminService::getInstance($w)->getUsers(["user.is_external" => 1, "user.is_deleted" => 0, "user.is_group" => 0], $external_page_number, $external_page_size, $external_sort, $external_sort_direction);
- $external_user_count = AdminService::getInstance($w)->countUsers(["is_external" => 1, "is_deleted" => 0, "is_group" => 0]);
+ if ($filter_applied === false) {
+ $external_users = AdminService::getInstance($w)->getUsers(["user.is_external" => 1, "user.is_deleted" => 0, "user.is_group" => 0], $external_page_number, $external_page_size, $external_sort, $external_sort_direction);
+ $external_user_count = AdminService::getInstance($w)->countUsers(["is_external" => 1, "is_deleted" => 0, "is_group" => 0]);
+ } elseif (!empty($filtered_ext_user_ids)) {
+ $external_users = AdminService::getInstance($w)->getUsers(["user.id" => $filtered_ext_user_ids], $external_page_number, $external_page_size, $external_sort, $external_sort_direction);
+ $external_user_count = AdminService::getInstance($w)->countUsers(["user.id" => $filtered_ext_user_ids]);
+ } else {
+ $external_users = null;
+ $external_user_count = 0;
+ $external_page_number = 1;
+ $w->sessionUnset("cmfive-external-users__page-number");
+ }
$internal_data = [];
if (!empty($internal_users)) {
@@ -29,7 +156,7 @@ function users_GET(Web $w)
$internal_data[] = [
($internal_user->is_locked ? '' : '') . $internal_user->login,
!empty($contact) ? $contact->firstname . ' ' . $contact->lastname : "",
- // !empty($contact) ? $contact->lastname : "",
+ !empty($contact) ? $contact->email : "",
$internal_user->is_admin ? "Yes" : "No",
$internal_user->is_active ? "Yes" : "No",
$internal_user->is_mfa_enabled ? "Yes" : "No",
@@ -38,7 +165,7 @@ function users_GET(Web $w)
HtmlBootstrap5::buttonGroup(
HtmlBootstrap5::b("/admin-user/edit/" . $internal_user->id, "Edit", null, "editbutton", false, 'btn-sm btn-secondary') .
HtmlBootstrap5::b("/admin/permissionedit/" . $internal_user->id, "Permissions", null, "permissionsbutton", false, 'btn-sm btn-info') .
- HtmlBootstrap5::b("/admin-user/remove/" . $internal_user->id, "Remove", null, "deletebutton", false, "btn-sm btn-danger")
+ HtmlBootstrap5::b("/admin-user/remove/" . $internal_user->id, "Delete", null, "deletebutton", false, "btn-sm btn-danger")
)
];
}
@@ -52,7 +179,8 @@ function users_GET(Web $w)
$external_data[] = [
$external_user->login,
!empty($contact->id) ? $contact->firstname . ' ' . $contact->lastname : 'No Contact object found',
- // !empty($contact->id) ? $contact->lastname : 'No Contact object found',
+ !empty($contact) ? $contact->email : "",
+ // !empty($contact->id) ? $contact->lastname : 'No Contact object found',
// [$external_user->is_admin ? "Yes" : "No", true],
// [$external_user->is_active ? "Yes" : "No", true],
AdminService::getInstance($w)->time2Dt($external_user->dt_created),
@@ -60,14 +188,17 @@ function users_GET(Web $w)
HtmlBootstrap5::buttonGroup(
HtmlBootstrap5::b("/admin-user/edit/" . $external_user->id, "Edit", null, "editbutton", false, 'btn-sm btn-secondary') .
HtmlBootstrap5::b("/admin/permissionedit/" . $external_user->id, "Permissions", null, "permissionsbutton", false, 'btn-sm btn-info') .
- HtmlBootstrap5::b("/admin-user/remove/" . $external_user->id, "Remove", null, "deletebutton", false, "btn-sm btn-danger")
+ HtmlBootstrap5::b("/admin-user/remove/" . $external_user->id, "Delete", null, "deletebutton", false, "btn-sm btn-danger")
)
];
}
}
- $internal_header = [['login', "Login"], ['name', "Name"], ['is_admin', "Admin"], ['is_active', "Active"], ['is_mfa_enabled', "MFA"], ['dt_lastlogin', "Last Login"], "Actions"];
- $external_header = [['login', "Login"], ['name', "Name"], ['dt_created', "Created"], "Actions"];
+ $internal_header = [['login', "Login"], ['name', "Name"], ['email', "Email"], ['is_admin', "Admin"], ['is_active', "Active"], ['is_mfa_enabled', "MFA"], ['dt_lastlogin', "Last Login"], "Actions"];
+ $external_header = [['login', "Login"], ['name', "Name"], ['email', "Email"], ['dt_created', "Created"], "Actions"];
+
+ //send variables to template
+ $w->ctx("filterData", $filterData);
$w->ctx("internal_table", HtmlBootstrap5::paginatedTable(
$internal_header,
@@ -75,7 +206,7 @@ function users_GET(Web $w)
$internal_page_number,
$internal_page_size,
$internal_user_count,
- "/admin/users",
+ $internal_base_url,
$internal_sort,
$internal_sort_direction,
'cmfive-internal-users__page-number',
@@ -91,7 +222,7 @@ function users_GET(Web $w)
$external_page_number,
$external_page_size,
$external_user_count,
- "/admin/users#external",
+ $external_base_url,
$external_sort,
$external_sort_direction,
'cmfive-external-users__page-number',
diff --git a/system/modules/admin/models/AuditInsight.php b/system/modules/admin/models/AuditInsight.php
index c6e759a2f..aa6815f37 100644
--- a/system/modules/admin/models/AuditInsight.php
+++ b/system/modules/admin/models/AuditInsight.php
@@ -14,12 +14,18 @@ public function getFilters(Web $w, $parameters = []): array
return [
"Options" => [
[
- [
- "Date From (required)", "date", "dt_from", array_key_exists('dt_from', $parameters) ? $parameters['dt_from'] : null
- ],
- [
- "Date To (required)", "date", "dt_to", array_key_exists('dt_to', $parameters) ? $parameters['dt_to'] : null
- ],
+ (new \Html\Form\InputField\Date([
+ 'id|name' => 'dt_from',
+ 'value' => array_key_exists('dt_from', $parameters) ? $parameters['dt_from'] : null,
+ 'label' => 'Date From',
+ 'required' => true,
+ ])),
+ (new \Html\Form\InputField\Date([
+ 'id|name' => 'dt_to',
+ 'value' => array_key_exists('dt_to', $parameters) ? $parameters['dt_to'] : null,
+ 'label' => 'Date To',
+ 'required' => true,
+ ])),
],
[
["Users (optional)", "select", "user_id", array_key_exists('user_id', $parameters) ? $parameters['user_id'] : null, AuthService::getInstance($w)->getUsers()],
diff --git a/system/modules/admin/models/Migration.php b/system/modules/admin/models/Migration.php
index 86e4262e4..599ab0383 100644
--- a/system/modules/admin/models/Migration.php
+++ b/system/modules/admin/models/Migration.php
@@ -17,5 +17,6 @@ class Migration extends DbObject {
* @var array[array[string]]
*/
public static $_validation = [
- 'name' => ['required']];
+ 'name' => ['required']
+ ];
}
\ No newline at end of file
diff --git a/system/modules/admin/models/MigrationService.php b/system/modules/admin/models/MigrationService.php
index 40e3d2059..ddf8e54d4 100644
--- a/system/modules/admin/models/MigrationService.php
+++ b/system/modules/admin/models/MigrationService.php
@@ -316,7 +316,7 @@ public function runMigrations($module, $filename = null, $ignoremessages = true,
// Class name must match filename after timestamp and hyphen
if (class_exists($migration['class_name'])) {
LogService::getInstance($this->w)->setLogger("MIGRATION")->info("Running migration: " . $migration['class_name']);
-
+
$this->w->db->startTransaction();
try {
// Set migration class
@@ -476,7 +476,7 @@ public function rollback($module, $filename)
//sort installed migrations by id largest to smallest
$migrations_to_rollback = $installed_migrations[$module];
usort($migrations_to_rollback, function ($a, $b) {
- return $a['id'] < $b['id'];
+ return $a['id'] - $b['id'];
});
// Attempt to rollback all migrations
diff --git a/system/modules/admin/templates/groups.tpl.php b/system/modules/admin/templates/groups.tpl.php
new file mode 100644
index 000000000..53f3d95a5
--- /dev/null
+++ b/system/modules/admin/templates/groups.tpl.php
@@ -0,0 +1,10 @@
+
+
+
+
-
-
Lookup List
+
+
-
-
- getLookupTypes())
+
+
+ getLookupTypes(), "form-select")
), "/admin/lookup"); ?>
-
-
+
\ No newline at end of file
diff --git a/system/modules/admin/templates/maintenance/index.tpl.php b/system/modules/admin/templates/maintenance/index.tpl.php
index b5625578f..e6836a97b 100644
--- a/system/modules/admin/templates/maintenance/index.tpl.php
+++ b/system/modules/admin/templates/maintenance/index.tpl.php
@@ -1,93 +1,79 @@
-
-
Server OS:
+
+
+
Server OS:
- Load: ' . implode(' ', $load) . '';
- } ?>
+ Load: ' . implode(' ', $load) . '';
+ } ?>
-
+
+
-
-
Auditing
-
+
+
+
+
Auditing
+
Export audit table to CSV
+
row in audit table
+
Exported audit logs will be removed from the database
+
+
-
Cache and Index
-
- -
-
-
-
- -
+
+
+
Cache and Index
+
+
-
+
images cached
-
-
-
-
-
-
-
searchable objects indexed
-
-
-
-
Printers
-
+
+
+
searchable objects indexed
+
+
+
+
+
+
Printers
+
Manage printers
+
printer found
+
View print queue
+
+
\ No newline at end of file
diff --git a/system/modules/admin/templates/migration/index.tpl.php b/system/modules/admin/templates/migration/index.tpl.php
index f36549b74..ce3b13c36 100644
--- a/system/modules/admin/templates/migration/index.tpl.php
+++ b/system/modules/admin/templates/migration/index.tpl.php
@@ -1,4 +1,6 @@
-
+
@@ -12,240 +14,300 @@
-
-
-
-
-
- -
- Not Installed
-
-
-
-
- $_not_installed) {
- foreach ($_not_installed as $_migration_class) {
- $migration_path = $_migration_class['path'];
- if (file_exists(ROOT_PATH . '/' . $migration_path)) {
- include_once ROOT_PATH . '/' . $migration_path;
- $classname = $_migration_class['class']['class_name'];
- if (class_exists($classname)) {
- $migration = (new $classname(1))->setWeb($w);
- $migration_description = $migration->description();
- $migration_preText = $migration->preText();
- $migration_postText = $migration->postText();
- }
- $row = [];
- $row[] = $module . ' - ' . $classname;
- $row[] = $migration_description;
- $row[] = $migration_preText;
- $row[] = $migration_postText;
- $data[] = $row;
+
+
+
+ Not Installed';
+ $active = false;
+ }
+
+ if (!empty($batched)) {
+ krsort($batched);
+
+ foreach ($batched as $batch_no => $batched_migrations) {
+ $id = "batch" . $batch_no . "-tab";
+ $control = "batch" . $batch_no;
+ $target = "#batch" . $batch_no;
+
+ echo '- Batch ' . $batch_no . '
';
+ $active = false;
+ }
+ }
+ ?>
+
+
+
+
+ ';
+
+ echo HtmlBootstrap5::b("/admin-migration/run/all?ignoremessages=false&prevpage=batch", "Install migrations", "Are you sure you want to install migrations?", null, false, "btn btn-sm btn-primary");
+ echo HtmlBootstrap5::b("/admin-migration/rollbackbatch", "Rollback latest batch", "Are you sure you want to rollback migrations?", null, false, "btn btn-sm btn-danger");
+
+
+ $active = false;
+ $header = ["Name", "Description", "Pre Text", "Post Text"];
+ $data = [];
+ foreach ($not_installed as $module => $_not_installed) {
+ foreach ($_not_installed as $_migration_class) {
+ $migration_path = $_migration_class['path'];
+ if (file_exists(ROOT_PATH . '/' . $migration_path)) {
+ include_once ROOT_PATH . '/' . $migration_path;
+ $classname = $_migration_class['class']['class_name'];
+ if (class_exists($classname)) {
+ $migration = (new $classname(1))->setWeb($w);
+ $migration_description = $migration->description();
+ $migration_preText = $migration->preText();
+ $migration_postText = $migration->postText();
}
+ $row = [];
+ $row[] = $module . ' - ' . $classname;
+ $row[] = $migration_description;
+ $row[] = $migration_preText;
+ $row[] = $migration_postText;
+ $data[] = $row;
}
}
+ }
- $table = Html::table($data, null, "tablesorter", $header);
- echo $table;
- ?>
-
-
- $batched_migrations) : ?>
-
-
- Batch
-
-
- ';
+ }
+
+ if (!empty($batched)) {
+ krsort($batched);
+
+ foreach ($batched as $batch_no => $batched_migrations) {
+ $id = "batch" . $batch_no . "-tab";
+ $control = "batch" . $batch_no;
+ $target = "#batch" . $batch_no;
+
+ echo '';
+ echo HtmlBootstrap5::b("/admin-migration/rollbackbatch", "Rollback latest batch", "Are you sure you want to rollback migrations?", null, false, "btn btn-sm btn-danger");
+
+ $active = false;
+ $header = ["Name", "Description", "Pre Text", "Post Text"];
+ $data = [];
+ foreach ($batched_migrations as $batched_migration) {
+ $row = [];
+ $row[] = $batched_migration['module'] . ' - ' . $batched_migration['classname'];
+ $row[] = $batched_migration['description'];
+ $row[] = $batched_migration['pretext'];
+ $row[] = $batched_migration['posttext'];
+ $data[] = $row;
+ }
+ echo HtmlBootstrap5::table($data, null, "table-striped", $header);
+ echo '
';
+ }
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+ $available_in_module) {
+ $id = $module . "-tab";
+ $target = "#" . $module;
+
+ echo '- ' . ucfirst($module);
+ $active = false;
+
+ // installed and non-installed migrations badges
+ $installed_count = is_array($installed[$module]) ? count($installed[$module]) : 0;
+ echo '';
+ echo $installed_count > 0 ? '' . $installed_count . '' : '';
+ echo (count($available_in_module) - $installed_count) > 0 ? '' . (count($available_in_module) - $installed_count) . '' : '';
+ echo '';
+
+ echo '
';
+ }
+ ?>
+
+
+
+
+ $available_in_module) {
+ $labelledby = $module . "-tab";
+ echo '
';
+ $active = false;
+
+ echo HtmlBootstrap5::box("/admin-migration/create/" . $module, "Create a" . (in_array($module[0], ['a', 'e', 'i', 'o', 'u']) ? 'n' : '') . ' ' . $module . " migration", true, false, null, null, null, null, "btn btn-sm btn-primary");
+
+ if (count($available[$module]) > 0) {
+ echo HtmlBootstrap5::b("/admin-migration/run/" . $module . "?ignoremessages=false&prevpage=individual", "Run all " . $module . " migrations", "Are you sure you want to run all outstanding migrations for this module?", null, false, "btn btn-sm btn-primary");
+ $header = ["Name", "Description", "Date run", "Pre Text", "Post Text", "Actions"];
$data = [];
- foreach ($batched_migrations as $batched_migration) {
+ foreach ($available_in_module as $a_migration_path => $migration_data) {
$row = [];
- $row[] = $batched_migration['module'] . ' - ' . $batched_migration['classname'];
- $row[] = $batched_migration['description'];
- $row[] = $batched_migration['pretext'];
- $row[] = $batched_migration['posttext'];
+ $row[] = $migration_data['class_name'];
+ $row[] = $migration_data['description'];
+ $row[] = MigrationService::getInstance($w)->isInstalled($migration_data['class_name']) ? "dt_created, "d-M-Y \a\\t H:i") . "'>Run " . Carbon::createFromTimeStamp(MigrationService::getInstance($w)->getMigrationByClassname($migration_data['class_name'])->dt_created)->diffForHumans() . " by " . (!empty(MigrationService::getInstance($w)->getMigrationByClassname($migration_data['class_name'])->creator_id) && !empty(AuthService::getInstance($w)->getUser(MigrationService::getInstance($w)->getMigrationByClassname($migration_data['class_name'])->creator_id)) ? AuthService::getInstance($w)->getUser(MigrationService::getInstance($w)->getMigrationByClassname($migration_data['class_name'])->creator_id)->getContact()->getFullName() : "System") . "" : "";
+ $row[] = $migration_data['pretext'];
+ $row[] = $migration_data['posttext'];
+
+ if (MigrationService::getInstance($w)->isInstalled($migration_data['class_name'])) {
+ $row[] = HtmlBootstrap5::b('/admin-migration/rollback/' . $module . '/' . basename($a_migration_path, ".php"), "Rollback to here", "Are you 110% sure you want to rollback a migration? DATA COULD BE LOST PERMANENTLY!", null, false, "btn btn-sm btn-danger");
+ } else {
+ $row[] = HtmlBootstrap5::b('/admin-migration/run/' . $module . '/' . basename($a_migration_path, ".php") . "?ignoremessages=false&prevpage=individual", "Migrate to here", "Are you sure you want to run a migration?", null, false, "btn btn-sm btn-primary");
+ }
+
$data[] = $row;
}
- $table = Html::table($data, null, "tablesorter", $header);
- echo $table;
- ?>
-
-
-
-
-
-
-
-
-
-
-
- $available_in_module) : ?>
-
-
- 0) : ?>
-
-
-
- Name | Description | Path | Date run | Pre Text | Post Text | Actions |
-
-
- $migration_data) : ?>
- isInstalled($migration_data['class_name'])) ? 'style="background-color: #43CD80;"' : ''; ?>>
- |
-
-
- |
- |
-
- isInstalled($migration_data['class_name'])) :
- $installedMigration = MigrationService::getInstance($w)->getMigrationByClassname($migration_data['class_name']); ?>
- ">
- Run dt_created)->diffForHumans(); ?> by creator_id) && !empty(AuthService::getInstance($w)->getUser($installedMigration->creator_id)) ? AuthService::getInstance($w)->getUser($installedMigration->creator_id)->getContact()->getFullName() : "System"; ?>
-
-
- |
-
-
- |
-
-
- |
-
- isInstalled($migration_data['class_name'])) {
- echo Html::b('/admin-migration/rollback/' . $module . '/' . $filename, "Rollback to here", "Are you 110% sure you want to rollback a migration? DATA COULD BE LOST PERMANENTLY!", null, false, "warning expand");
- } else {
- echo Html::b('/admin-migration/run/' . $module . '/' . $filename. "?ignoremessages=false&prevpage=individual", "Migrate to here", "Are you sure you want to run a migration?", null, false, "info expand");
- }
- ?>
- |
-
-
-
-
-
-
-
-
-
-
+ echo '
';
+ }
+ ?>
+
+
+
There are no migrations on this project
-
-
- $available_seeds) : ?>
- 0) : ?>
- -
-
-
-
-
-
-
- $available_seeds) : ?>
-
-
-
-
- Name |
- Description |
- Status |
- Action |
-
-
-
- $classname) : ?>
-
+
-
+ }
+ }
+
+ $seed_status_counts[$module] = [$installed, $not_installed];
+ }
+
+ $active = true;
+ foreach ($seeds as $module => $available_seeds) {
+ $id = $module . "-tab-seed";
+ $target = "#" . $module . "-seed";
+ $seedmodule = $module . "-seed";
+
+ echo '
' . ucfirst($module);
+ $active = false;
+
+ // installed and non-installed migrations badges
+ echo '';
+ echo $seed_status_counts[$module][0] > 0 ? '' . $seed_status_counts[$module][0] . '' : '';
+ echo $seed_status_counts[$module][1] > 0 ? '' . $seed_status_counts[$module][1] . '' : '';
+ echo '';
+
+ echo '';
+ }
+ ?>
+
+
+
+
+
+ $available_seeds) {
+ $id = $module . "-tab-seed";
+ $seedmodule = $module . "-seed";
+
+ echo '
';
+ $active = false;
+
+ $header = ["Name", "Description", "Status", "Action"];
+ $data = [];
+
+ foreach ($available_seeds as $seed => $classname) {
+ if (is_file($seed)) {
+ require_once($seed);
+
+ $seed_obj = null;
+ if (class_exists($classname)) {
+ $seed_obj = new $classname($w);
+ }
+ }
+
+ if (!empty($seed_obj)) {
+ $migration_exists = MigrationService::getInstance($w)->migrationSeedExists($classname);
+ $row = [];
+ $row[] = $seed_obj->name;
+ $row[] = $seed_obj->description;
+ $row[] = $migration_exists ? "Installed" : "Not installed";
+ $row[] = !$migration_exists ? HtmlBootstrap5::b('/admin-migration/installseed?url=' . urlencode($seed), "Install", null, null, false, "btn btn-sm btn-primary") : '';
+ $data[] = $row;
+ }
+ }
+
+ echo HtmlBootstrap5::table($data, null, "table-striped center-3rd-column", $header);
+ echo '
';
+ }
+ ?>
-
+
-
+
\ No newline at end of file
diff --git a/system/modules/admin/templates/permissionedit.tpl.php b/system/modules/admin/templates/permissionedit.tpl.php
index 7bb0a226a..616e37544 100755
--- a/system/modules/admin/templates/permissionedit.tpl.php
+++ b/system/modules/admin/templates/permissionedit.tpl.php
@@ -1,12 +1,13 @@
-
+
\ No newline at end of file
diff --git a/system/modules/admin/templates/printers.tpl.php b/system/modules/admin/templates/printers.tpl.php
index 1984983a6..e63854864 100755
--- a/system/modules/admin/templates/printers.tpl.php
+++ b/system/modules/admin/templates/printers.tpl.php
@@ -1,3 +1,3 @@
-
-
-
-
-
-
-
-
-
- this is the template manual
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is the template manual
+
+
+
\ No newline at end of file
diff --git a/system/modules/admin/templates/templates/index.tpl.php b/system/modules/admin/templates/templates/index.tpl.php
index c2ce81029..08810ca8f 100755
--- a/system/modules/admin/templates/templates/index.tpl.php
+++ b/system/modules/admin/templates/templates/index.tpl.php
@@ -1,2 +1,2 @@
-
+
diff --git a/system/modules/admin/templates/user/edit.tpl.php b/system/modules/admin/templates/user/edit.tpl.php
index 6d44fdd3c..81caa3429 100644
--- a/system/modules/admin/templates/user/edit.tpl.php
+++ b/system/modules/admin/templates/user/edit.tpl.php
@@ -1,218 +1,106 @@
Edit - {{ user.account.firstname + ' ' + user.account.lastname }}
-
-
-
-
-
-
Personal
-
-
-
-
-
-
-
-
Contact
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
Update Password
-
-
-
-
-
Two Factor Authentication
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/system/modules/channels/templates/listchannels.tpl.php b/system/modules/channels/templates/listchannels.tpl.php
index 54e1dfe28..8c904e4c3 100755
--- a/system/modules/channels/templates/listchannels.tpl.php
+++ b/system/modules/channels/templates/listchannels.tpl.php
@@ -1,5 +1,6 @@
-
-
+
+
+
@@ -22,12 +23,20 @@
name; ?> |
is_active ? "Yes" : "No"; ?> |
- _channeltype}/edit/{$base_channel->id}", "Edit", true); ?>
- _channeltype}/delete/{$base_channel->id}", "Delete", "Are you sure you want to delete " . (!empty($base_channel->name) ? 'the ' . addslashes($base_channel->name) . ' channel' : "this channel") . "?"); ?>
- id}", "Messages", true); ?>
- _channeltype == 'email') {
- echo Html::box("/channels-email/test/{$base_channel->id}", 'Test Connection', true);
- } ?>
+ _channeltype}/edit/{$base_channel->id}", "Edit", null, null, false, "btn btn-secondary") .
+ HtmlBootstrap5::dropdownButton(
+ "More",
+ [
+ HtmlBootstrap5::b("/channels/listmessages/{$base_channel->id}", "Messages", null, null, false, "dropdown-item btn-sm text-start"),
+ ($c->_channeltype == 'email' ? HtmlBootstrap5::b("/channels-email/test/{$base_channel->id}", 'Test Connection', null, null, false, "dropdown-item btn-sm text-start") : ''),
+ ' ',
+ HtmlBootstrap5::b("/channels-{$c->_channeltype}/delete/{$base_channel->id}", "Delete", "Are you sure you want to delete " . (!empty($base_channel->name) ? 'the ' . addslashes($base_channel->name) . ' channel' : "this channel") . "?", null, false, "dropdown-item btn-sm text-start text-danger")
+ ],
+ "btn-info btn btn-sm rounded-0 rounded-end-1"
+ )
+ ); ?>
|
diff --git a/system/modules/channels/templates/listprocessors.tpl.php b/system/modules/channels/templates/listprocessors.tpl.php
index c1fedc07b..cca936e28 100755
--- a/system/modules/channels/templates/listprocessors.tpl.php
+++ b/system/modules/channels/templates/listprocessors.tpl.php
@@ -22,21 +22,33 @@
module; ?> |
name) ? $channel->name : ""; ?> |
- id}", "Edit", true); ?>
- id}", "Delete", true, null, "Are you sure you want to delete " . (!empty($p->name) ? $p->name : "this processor") . "?"); ?>
class)) {
$class = new $p->class($w);
if (method_exists($class, "getSettingsForm")) {
$form = $class->getSettingsForm($p->settings);
if (!empty($form)) {
- echo Html::box("/channels-processor/editsettings/{$p->id}", "Settings", true);
+ $editsettings = HtmlBootstrap5::box("/channels-processor/editsettings/{$p->id}", "Settings", true, false, null, null, "isbox", null, "dropdown-item btn-sm text-start");
}
}
} else {
LogService::getInstance($w)->setLogger('CHANNEL')->error('Processor class ' . $p->class . ' not found');
}
+
+ echo HtmlBootstrap5::buttonGroup(
+ HtmlBootstrap5::box("/channels-processor/edit/{$p->id}", "Edit", true, false, null, null, "isbox", null, "btn-sm btn-secondary") .
+ HtmlBootstrap5::dropdownButton(
+ "More",
+ [
+ $editsettings,
+ $editsettings != "" ? ' ' : "",
+ HtmlBootstrap5::b("/channels-processor/delete/{$p->id}", "Delete", "Are you sure you want to delete " . (!empty($p->name) ? $p->name : "this processor") . "?", null, false, "dropdown-item btn-sm text-start text-danger")
+ ],
+ "btn-info btn btn-sm rounded-0 rounded-end-1"
+ )
+ );
?>
|
diff --git a/system/modules/channels/templates/web/edit.tpl.php b/system/modules/channels/templates/web/edit.tpl.php
index a30ba5288..acd626be1 100644
--- a/system/modules/channels/templates/web/edit.tpl.php
+++ b/system/modules/channels/templates/web/edit.tpl.php
@@ -1,26 +1 @@
-
-
\ No newline at end of file
diff --git a/system/modules/channels/tests/acceptance/playwright/channels.test.ts b/system/modules/channels/tests/acceptance/playwright/channels.test.ts
new file mode 100644
index 000000000..d065905a4
--- /dev/null
+++ b/system/modules/channels/tests/acceptance/playwright/channels.test.ts
@@ -0,0 +1,91 @@
+import { expect, test } from "@playwright/test";
+import { GLOBAL_TIMEOUT, CmfiveHelper } from "@utils/cmfive";
+import { ChannelsHelper} from "@utils/channels";
+
+// test.describe.configure({mode: 'parallel'});
+
+test("Test that you can create a Web Channel using the Channels module", async ({ page }) => {
+ test.setTimeout(GLOBAL_TIMEOUT);
+ CmfiveHelper.acceptDialog(page);
+
+ await CmfiveHelper.login(page, "admin", "admin");
+
+ // check that you can create a web channel
+ const channel = {
+ "Name": CmfiveHelper.randomID("channel_"),
+ "Run processors?": false,
+ "Web API URL": "url"
+ }
+
+ await ChannelsHelper.createWebChannel(page, channel);
+ await ChannelsHelper.verifyWebChannel(page, channel);
+
+ // check that you can edit a web channel
+ const editedChannel = await ChannelsHelper.editWebChannel(page, channel, {
+ "Name": channel["Name"]+"_edited",
+ "Is Active": false,
+ "Run processors?": true,
+ "Web API URL": "url_edited"
+ });
+ await ChannelsHelper.verifyWebChannel(page, editedChannel);
+
+ // check that you can create a processor
+ await ChannelsHelper.createProcessor(page, editedChannel["Name"]+"_processor", editedChannel["Name"] as string, "channels.TestProcessor");
+ await ChannelsHelper.verifyProcessor(page, editedChannel["Name"]+"_processor", editedChannel["Name"] as string, "channels.TestProcessor");
+
+ // check that you can delete a processor
+ await ChannelsHelper.deleteProcessor(page, editedChannel["Name"]+"_processor");
+ await expect(page.getByText(editedChannel["Name"]+"_processor")).not.toBeVisible();
+
+ // check that you can delete a web channel
+ await ChannelsHelper.deleteChannel(page, editedChannel);
+});
+
+test("Test that you can create an Email Channel using the Channels module", async ({ page }) => {
+ test.setTimeout(GLOBAL_TIMEOUT);
+ CmfiveHelper.acceptDialog(page);
+
+ await CmfiveHelper.login(page, "admin", "admin");
+
+ // check that you can create an email channel
+ const channel = {
+ "Name": CmfiveHelper.randomID("channel_"),
+ "Protocol": "POP3",
+ "Server URL": "url",
+ "Username": "username",
+ "Password": "password",
+ "Is Active": false,
+ "Verify Peer": false,
+ "Allow self signed certificates": true,
+ }
+
+ await ChannelsHelper.createEmailChannel(page, channel);
+ await ChannelsHelper.verifyEmailChannel(page, channel);
+
+ // check that you can edit an email channel
+ const editedChannel = await ChannelsHelper.editEmailChannel(page, channel, {
+ "Name": channel["Name"]+"_edited",
+ "Is Active": true,
+ "Run processors?": false,
+ "Protocol": "IMAP",
+ "Server URL": "url_edited",
+ "Username": "username_edited",
+ "Password": "password_edited",
+ "Port": "1234",
+ "Use Auth?": false,
+ "Verify Peer": true,
+ "Allow self signed certificates": false,
+ "Folder": "folder",
+ "To": "to",
+ "From": "from",
+ "Subject": "subject",
+ "Body": "body",
+ "CC": "cc",
+ "Post Read Action": "Archive",
+ "Post Read Data": "post read data",
+ });
+ await ChannelsHelper.verifyEmailChannel(page, editedChannel);
+
+ // check that you can delete an email channel
+ await ChannelsHelper.deleteChannel(page, editedChannel);
+});
\ No newline at end of file
diff --git a/system/modules/channels/tests/acceptance/playwright/channels.utils.ts b/system/modules/channels/tests/acceptance/playwright/channels.utils.ts
new file mode 100644
index 000000000..d78050287
--- /dev/null
+++ b/system/modules/channels/tests/acceptance/playwright/channels.utils.ts
@@ -0,0 +1,338 @@
+import { Page, Locator, expect } from "@playwright/test";
+import { CmfiveHelper, HOST } from "@utils/cmfive";
+
+const webChannelFormLabelToId = {
+ "Name": "#name",
+ "Web API URL": "#url",
+ "Run processors?": "#do_processing",
+ "Is Active": "#is_active"
+}
+
+const emailChannelFormLabelToId = {
+ "Name": "#name",
+ "Is Active": "#is_active",
+ "Run processors?": "#do_processing",
+ "Protocol": "#protocol",
+ "Server URL": "#server",
+ "Username": "#s_username",
+ "Password": "#s_password",
+ "Port": "#port",
+ "Use Auth?": "#use_auth",
+ "Verify Peer": "#verify_peer",
+ "Allow self signed certificates": "#allow_self_signed",
+ "Folder": "#folder",
+ "To": "#to_filter",
+ "From": "#from_filter",
+ "Subject": "#subject_filter",
+ "CC": "#cc_filter",
+ "Body": "#body_filter",
+ "Post Read Data": "#post_read_parameter",
+ "Post Read Action": "#post_read_action"
+}
+
+const processorFormLabelToId = {
+ "Name": "#name",
+ "Channel": "#channel_id",
+ "Processor Class": "#processor_class"
+}
+
+const getFormElementByLabel =
+ (page: Page | Locator, labelToId: Record) =>
+ (label: string) => page.locator(labelToId[label])
+
+export class ChannelsHelper {
+ static async createWebChannel(page: Page, channel: Record)
+ {
+ expect(typeof channel["Name"] == "string" && channel["Name"] != "").toBeTruthy();
+
+ // IMPURE! THIS SUCKS! SHOULD NOT BE USING `page.goto`
+ // this pattern for grabbing the modal works in every other test that encounters modals
+ // why doesn't it work here?
+ /*
+ await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ await page.getByRole("button", { name: "Add Web Channel" }).click();
+ await page.waitForSelector("#cmfive-modal");
+ const modal = page.locator("#cmfive-modal");
+ */
+ // Timeouts because why not.
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-web/edit");
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, webChannelFormLabelToId);
+
+ for(let option of ["Name", "Web API URL"])
+ if (channel[option] !== undefined)
+ await formElement(option).fill(channel[option] as string);
+
+ for(let option of ["Is Active", "Run processors?"])
+ if(channel[option] !== undefined) {
+ if (channel[option] === true)
+ await formElement(option).check();
+ else if (channel[option] === false)
+ await formElement(option).uncheck();
+ }
+
+ await page.getByRole("button", {name: "Save"}).click();
+ }
+
+ static async editWebChannel(page: Page, channel: Record, edit: Record): Promise>
+ {
+ expect(edit["Name"] === undefined || (typeof edit["Name"] == "string" && edit["Name"] != "")).toBeTruthy();
+
+ // await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ // await CmfiveHelper.getRowByText(page, channel["Name"] as string).getByRole("button", {name: "Edit"}).click();
+ // await page.waitForSelector("#cmfive-modal", {state: "visible"});
+ // const modal = page.locator("#cmfive-modal");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-web/edit/1");
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, webChannelFormLabelToId);
+
+ for(let option of ["Name", "Web API URL"])
+ if (edit[option] !== undefined)
+ await formElement(option).fill(edit[option] as string);
+
+ for(let option of ["Is Active", "Run processors?"])
+ if(edit[option] !== undefined) {
+ if (edit[option] === true)
+ await formElement(option).check();
+ else if (edit[option] === false)
+ await formElement(option).uncheck();
+ }
+
+ await page.getByRole("button", {name: "Save"}).click();
+
+ let editedChannel: Record = {};
+ for(let option of ["Name", "Web API URL", "Is Active", "Run processors?"])
+ if (edit[option] !== undefined)
+ editedChannel[option] = edit[option];
+ else
+ editedChannel[option] = channel[option];
+
+ return editedChannel;
+ }
+
+ static async verifyWebChannel(page: Page, channel: Record)
+ {
+ // await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ // await CmfiveHelper.getRowByText(page, channel["Name"] as string).getByRole("button", { name: "Edit" }).click();
+ // await page.waitForSelector("#cmfive-modal", {state: "visible"});
+ // const modal = page.locator("#cmfive-modal");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-web/edit/1");
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, webChannelFormLabelToId);
+
+ for(let option of ["Name", "Web API URL"])
+ if (channel[option] !== undefined)
+ await expect(formElement(option)).toHaveValue(channel[option] as string);
+ else
+ await expect(formElement(option)).toHaveValue("");
+
+ for(let option of ["Is Active", "Run processors?"])
+ if(channel[option] !== undefined) {
+ if (channel[option] === true)
+ expect(await formElement(option).isChecked()).toBeTruthy();
+ else if (channel[option] === false)
+ expect(await formElement(option).isChecked()).toBeFalsy();
+ } // web channel defaults to true for both Is Active and Run Processors? if not specified
+ else
+ expect(formElement(option).isChecked()).toBeTruthy();
+
+ await page.getByRole("button", {name: "Cancel"}).click();
+ }
+
+ static async createEmailChannel(page: Page, channel: Record)
+ {
+ for(let required of ["Name", "Protocol", "Server URL", "Username", "Password"])
+ await expect(typeof channel[required] == "string" && channel[required] != "").toBeTruthy();
+
+ // await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ // await page.getByRole("button", { name: "Add Email Channel" }).click();
+ // await page.waitForSelector("#cmfive-modal.show");
+ // const modal = page.locator("#cmfive-modal.show");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-email/edit");
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, emailChannelFormLabelToId);
+
+ await formElement("Protocol").selectOption(channel["Protocol"] as string);
+
+ if(channel["Post Read Action"] !== undefined)
+ await formElement("Post Read Action").selectOption(channel["Post Read Action"] as string);
+
+ if (channel["Port"] !== undefined)
+ await formElement("Port").fill(channel["Port"] as string);
+
+ for(let option of ["Name", "Server URL", "Username", "Password"])
+ await formElement(option).fill(channel[option] as string);
+
+ for(let option of ["Folder", "To", "From", "Subject", "CC", "Body", "Post Read Data"])
+ if (channel[option] !== undefined)
+ await formElement(option).fill(channel[option] as string);
+
+ for(let option of ["Is Active", "Run processors?", "Use Auth?", "Verify Peer", "Allow self signed certificates"])
+ if(channel[option] !== undefined) {
+ if (channel[option] === true)
+ await formElement(option).check();
+ else if (channel[option] === false)
+ await formElement(option).uncheck();
+ }
+
+ await page.getByRole("button", {name: "Save"}).click();
+ }
+
+ static async editEmailChannel(page: Page, channel: Record, edit: Record)
+ {
+ for(let required of ["Name", "Protocol", "Server URL", "Username", "Password"])
+ await expect(edit[required] === undefined || (typeof edit[required] == "string" && edit[required] != "")).toBeTruthy();
+
+ // await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ // await CmfiveHelper.getRowByText(page, channel["Name"] as string).getByRole("button", {name: "Edit"}).click();
+ // await page.waitForSelector("#cmfive-modal.show");
+ // const modal = page.locator("#cmfive-modal.show");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-email/edit/2"); // 2 because this is second channel created, channel tests are sequential
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, emailChannelFormLabelToId);
+
+ if (edit["Port"] !== undefined)
+ await formElement("Port").fill(edit["Port"] as string);
+
+ for(let option of ["Protocol", "Post Read Action"])
+ if (edit[option] !== undefined)
+ await formElement(option).selectOption(edit[option] as string);
+
+ for(let option of ["Name", "Server URL", "Username", "Password"])
+ if (edit[option] !== undefined)
+ await formElement(option).fill(edit[option] as string);
+
+ for(let option of ["Folder", "To", "From", "Subject", "CC", "Body", "Post Read Data"])
+ if (edit[option] !== undefined)
+ await formElement(option).fill(edit[option] as string);
+
+ for(let option of ["Is Active", "Run processors?", "Use Auth?", "Verify Peer", "Allow self signed certificates"])
+ if(edit[option] !== undefined)
+ if (edit[option] === true)
+ await formElement(option).check();
+ else if (edit[option] === false)
+ await formElement(option).uncheck();
+
+ await page.getByRole("button", {name: "Save"}).click();
+
+ let editedChannel: Record = {};
+ for(let option of ["Name", "Protocol", "Server URL", "Username", "Password", "Port", "Folder", "To", "From", "Subject", "CC", "Body", "Post Read Data", "Post Read Action", "Is Active", "Run processors?", "Use Auth?", "Verify Peer", "Allow self signed certificates"])
+ if (edit[option] !== undefined)
+ editedChannel[option] = edit[option];
+ else if (channel[option] !== undefined)
+ editedChannel[option] = channel[option];
+
+ return editedChannel;
+ }
+
+ static async verifyEmailChannel(page: Page, channel: Record)
+ {
+ // await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ // await CmfiveHelper.getRowByText(page, channel["Name"] as string).getByRole("button", {name: "Edit"}).click();
+ // await page.waitForSelector("#cmfive-modal.show");
+ // const modal = page.locator("#cmfive-modal.show");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-email/edit/2"); // 2 because this is second channel created, channel tests are sequential
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, emailChannelFormLabelToId);
+
+ await expect(formElement("Protocol")).toHaveValue(channel["Protocol"] as string);
+
+ if (channel["Post Read Action"] !== undefined)
+ await expect(formElement("Post Read Action")).toHaveValue(channel["Post Read Action"] as string);
+ else
+ await expect(await formElement("Post Read Action").locator('option[selected="true"]').count()).toBe(0);
+
+ if (channel["Port"] !== undefined)
+ await expect(formElement("Port")).toHaveValue(channel["Port"] as string);
+ else
+ await expect(formElement("Port")).toHaveValue("");
+
+ for(let option of ["Name", "Server URL", "Username", "Password"])
+ await expect(formElement(option)).toHaveValue(channel[option] as string);
+
+ for(let option of ["Folder", "To", "From", "Subject", "CC", "Body", "Post Read Data"])
+ if (channel[option] !== undefined)
+ await expect(formElement(option)).toHaveValue(channel[option] as string);
+ else
+ await expect(formElement(option)).toHaveValue("");
+
+ for(let option of ["Is Active", "Run processors?", "Use Auth?", "Verify Peer", "Allow self signed certificates"])
+ if(channel[option] !== undefined) {
+ if (channel[option] === true)
+ await expect(await formElement(option).isChecked()).toBeTruthy();
+ else if (channel[option] === false)
+ await expect(await formElement(option).isChecked()).toBeFalsy();
+ }
+
+ for(let option of ["Is Active", "Run processors?"])
+ if (channel[option] === undefined)
+ await expect(formElement(option).isChecked()).toBeTruthy();
+
+ await page.getByRole("button", {name: "Cancel"}).click();
+ }
+
+ static async deleteChannel(page: Page, channel: Record)
+ {
+ await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Channels");
+ await CmfiveHelper.getRowByText(page, channel["Name"] as string).getByRole("button", { name: "More" }).click();
+ await page.getByRole("button", {name: "Delete"}).click();
+ await expect(page.getByText("Channel deleted")).toBeVisible();
+ }
+
+ static async createProcessor(page: Page, processorName: string, channel: string, processorClass: string)
+ {
+ await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Processors");
+ // await page.getByRole("button", {name: "Add Processor"}).click();
+ // await page.waitForSelector("#cmfive-modal", {state: "visible"});
+ // const modal = page.locator("#cmfive-modal");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-processor/edit");
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, processorFormLabelToId);
+
+ await formElement("Name").fill(processorName);
+ await formElement("Channel").selectOption(channel);
+ await formElement("Processor Class").selectOption(processorClass);
+
+ await page.getByRole("button", {name: "Save"}).click();
+ }
+
+ static async verifyProcessor(page: Page, processorName: string, channel: string, processorClass: string)
+ {
+ // await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Processors");
+ // await CmfiveHelper.getRowByText(page, processorName).getByRole("button", {name: "Edit"}).click(); // if this doesn't fail, name is verified as correct
+ // await page.waitForSelector("#cmfive-modal", {state: "visible"});
+ // const modal = page.locator("#cmfive-modal");
+ await page.waitForTimeout(500);
+ await page.goto(HOST + "/channels-processor/edit/1");
+ await page.waitForTimeout(500);
+
+ const formElement = getFormElementByLabel(page, processorFormLabelToId);
+
+ await expect(formElement("Channel").locator('option[selected="true"]')).toHaveText(channel);
+ await expect(formElement("Processor Class").locator('option[selected="true"]')).toHaveText(processorClass);
+
+ await page.getByRole("button", {name: "Cancel"}).click();
+ }
+
+ static async deleteProcessor(page: Page, processorName: string)
+ {
+ await CmfiveHelper.clickCmfiveNavbar(page, "Channels", "List Processors");
+ await CmfiveHelper.getRowByText(page, processorName).getByRole("button", { name: "More" }).click();
+ await page.getByRole("button", {name: "Delete"}).click();
+ page.reload();
+ }
+}
\ No newline at end of file
diff --git a/system/modules/channels/tests/acceptance/playwright/tsconfig.json b/system/modules/channels/tests/acceptance/playwright/tsconfig.json
new file mode 100644
index 000000000..b6f7ee048
--- /dev/null
+++ b/system/modules/channels/tests/acceptance/playwright/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "es2015",
+ "dom"
+ ],
+ "baseUrl": "../../../../../../../cmfive-boilerplate/test/playwright",
+ "paths": {
+ "*": [
+ "*",
+ "node_modules/*"
+ ],
+ "@utils/*": [
+ "src/utils/*"
+ ],
+ "@utils/cmfive": [
+ "cmfive.utils.ts"
+ ]
+ }
+ }
+}
diff --git a/system/modules/favorite/partials/actions/display_favorites.php b/system/modules/favorite/partials/actions/display_favorites.php
index 01697e5e2..1346900e8 100755
--- a/system/modules/favorite/partials/actions/display_favorites.php
+++ b/system/modules/favorite/partials/actions/display_favorites.php
@@ -4,7 +4,8 @@
function display_favorites(\Web $w)
{
- $results = FavoriteService::getInstance($w)->getFavoritesForUser(AuthService::getInstance($w)->user()->id);
+ $results = \FavoriteService::getInstance($w)
+ ->getFavoritesForUser(\AuthService::getInstance($w)->user()->id);
$categorisedFavorites = array();
if (!empty($results)) {
foreach ($results as $k => $favorite) {
diff --git a/system/modules/file/actions/admin/index.php b/system/modules/file/actions/admin/index.php
index 2e407951e..59cb367eb 100644
--- a/system/modules/file/actions/admin/index.php
+++ b/system/modules/file/actions/admin/index.php
@@ -2,6 +2,8 @@
function index_GET(Web $w)
{
+ $w->setLayout('layout-bootstrap-5');
+
$w->ctx('title', 'File adapter management');
// Get attachments
diff --git a/system/modules/file/actions/admin/moveToAdapter.php b/system/modules/file/actions/admin/moveToAdapter.php
index 3cc4f6af0..60cf5b91b 100644
--- a/system/modules/file/actions/admin/moveToAdapter.php
+++ b/system/modules/file/actions/admin/moveToAdapter.php
@@ -4,6 +4,11 @@ function moveToAdapter_GET(Web $w)
{
$from_adapter = Request::string('from_adapter');
$to_adapter = Request::string('to_adapter');
+ $max_count = Request::string('max_count');
+
+ if (empty($max_count)) {
+ $max_count = -1;
+ }
if (!empty(Config::get('file.adapters.' . $to_adapter)) && Config::get('file.adapters.' . $to_adapter . '.active') === true) {
if (empty(Config::get('file.adapters.' . $from_adapter))) {
@@ -12,14 +17,27 @@ function moveToAdapter_GET(Web $w)
// From index
$count = 0;
+ $skipped = 0;
$attachments = FileService::getInstance($w)->getAttachmentsForAdapter($from_adapter);
if (!empty($attachments)) {
foreach ($attachments as $attachment) {
- $attachment->moveToAdapter($to_adapter);
- $count++;
+ if ($max_count >= 0 && $count >= $max_count) {
+ break;
+ }
+ try {
+ $attachment->moveToAdapter($to_adapter);
+ $count++;
+ } catch (Exception $e) {
+ LogService::getInstance($w)->error($e->getMessage());
+ $skipped++;
+ }
}
}
- $w->msg($count . ' attachment' . ($count == 1 ? '' : 's') . ' moved from "' . $from_adapter . '" to "' . $to_adapter . '"', '/file-admin');
+ $message = ($count . ' attachment' . ($count == 1 ? '' : 's') . ' moved from "' . $from_adapter . '" to "' . $to_adapter . '"');
+ if ($skipped > 0) {
+ $message = $message . ('
' . $skipped . ' attachment' . ($skipped == 1 ? '' : 's') . ' skipped, check logs.');
+ }
+ $w->msg($message, '/file-admin');
} else {
$w->error('Target adapter "' . $to_adapter . '" is either not found or not active', '/file-admin');
}
diff --git a/system/modules/file/actions/deletedfiles.php b/system/modules/file/actions/deletedfiles.php
index 1e3d97f0c..8ee126438 100644
--- a/system/modules/file/actions/deletedfiles.php
+++ b/system/modules/file/actions/deletedfiles.php
@@ -1,7 +1,10 @@
setLayout('layout-bootstrap-5');
- $w->ctx("deleted_files", FileService::getInstance($w)->getObjects("Attachment", ["is_deleted" => 1]));
+ // Need to use $includeDeleted
+ $w->ctx("deleted_files", FileService::getInstance($w)->getObjects("Attachment", ["is_deleted" => 1], false, true, null, null, null, true));
}
diff --git a/system/modules/file/actions/edit.php b/system/modules/file/actions/edit.php
index 81398f8d1..eb1bf9f91 100644
--- a/system/modules/file/actions/edit.php
+++ b/system/modules/file/actions/edit.php
@@ -52,5 +52,6 @@ function edit_GET(Web $w) {
$w->ctx("is_restricted", json_encode(empty($owner) ? false : true));
$w->ctx("viewers", json_encode($viewers));
$w->ctx("owner", json_encode(["id" => empty($owner) ? null : $owner->id, "name" => empty($owner) ? null : $owner->getFullName()]));
+ $w->ctx("show_restrict", Request::string("allowrestrictionui"));
$w->ctx("can_restrict", AuthService::getInstance($w)->user()->hasRole("restrict") ? "true" : "false");
}
\ No newline at end of file
diff --git a/system/modules/file/models/Attachment.php b/system/modules/file/models/Attachment.php
index 4240d6978..d66c82141 100755
--- a/system/modules/file/models/Attachment.php
+++ b/system/modules/file/models/Attachment.php
@@ -496,8 +496,8 @@ public function updateAttachment($requestkey)
if ($this->isImage()) {
// Generate thumbnail and cache
require_once 'phpthumb/ThumbLib.inc.php';
- $width = $this->w->request("w", FileService::$_thumb_width);
- $height = $this->w->request("h", FileService::$_thumb_height);
+ $width = Request::int('w', FileService::$_thumb_width);
+ $height = Request::int('h', FileService::$_thumb_height);
$thumb = PhpThumbFactory::create($this->getContent(), [], true);
$thumb->adaptiveResize($width, $height);
diff --git a/system/modules/file/partials/actions/attachment_item.php b/system/modules/file/partials/actions/attachment_item.php
index b4100f2c1..2a6c93b2e 100644
--- a/system/modules/file/partials/actions/attachment_item.php
+++ b/system/modules/file/partials/actions/attachment_item.php
@@ -8,5 +8,6 @@ function attachment_item_GET(\Web $w, $params)
$w->ctx("owner", \RestrictableService::getInstance($w)->getOwner($params["attachment"]));
$w->ctx("redirect", $params["redirect"]);
$w->ctx("image_data_blocked", $params["hide_image_exif"] ?? false);
+ $w->ctx("edit_attachment_restrictions_blocked", $params["hide_edit_restriction"] ?? false);
$w->ctx("is_mutable", $params["is_mutable"] ?? true);
}
diff --git a/system/modules/file/partials/actions/listattachments.php b/system/modules/file/partials/actions/listattachments.php
index 642926594..d9bf874e3 100755
--- a/system/modules/file/partials/actions/listattachments.php
+++ b/system/modules/file/partials/actions/listattachments.php
@@ -29,6 +29,7 @@ function listattachments(\Web $w, $params)
"attachment" => $attachment,
"redirect" => $redirect,
"hide_image_exif" => $params["hide_image_exif"] ?? false,
+ "hide_edit_restriction" => $params["hide_edit_restriction"] ?? false,
"is_mutable" => $params["is_mutable"] ?? true,
], "file", "GET");
}
diff --git a/system/modules/file/partials/templates/attachment_item.tpl.php b/system/modules/file/partials/templates/attachment_item.tpl.php
index bb8ac2419..92ed404d0 100644
--- a/system/modules/file/partials/templates/attachment_item.tpl.php
+++ b/system/modules/file/partials/templates/attachment_item.tpl.php
@@ -7,7 +7,7 @@
id . "?redirect_url=" . urlencode($redirect), "Edit", true, null, null, null, null, null, "button expand secondary"))
+ ? (Html::box("/file/edit/" . $attachment->id . "?allowrestrictionui=" . empty($edit_attachment_restrictions_blocked) . "&redirect_url=" . urlencode($redirect), "Edit", true, null, null, null, null, null, "button expand secondary"))
: ""; ?>
diff --git a/system/modules/file/templates/admin/index.tpl.php b/system/modules/file/templates/admin/index.tpl.php
index a1afaac06..560ee663b 100644
--- a/system/modules/file/templates/admin/index.tpl.php
+++ b/system/modules/file/templates/admin/index.tpl.php
@@ -1,22 +1,22 @@
- $sorted_attachments) : ?>
- -
-
; border: 1px solid #ccc; padding: 20px;'>
-
-
attachment
+ $sorted_attachments) : ?>
+
-
+
; border: 1px solid #ccc; padding: 20px;'>
+
+ attachment
- 0) {
- foreach ($attachments as $_adapter => $_att) {
- if ($_adapter !== $attachment_adapter && Config::get('file.adapters.' . $_adapter . '.active') == true) {
- echo Html::b('/file-admin/moveToAdapter?from_adapter=' . $attachment_adapter . '&to_adapter=' . $_adapter, 'Move all to ' . strtoupper($_adapter), 'Are you sure you want to move all attachments from ' . $attachment_adapter . ' to ' . $_adapter, null, false, 'secondary expand');
+ 0) {
+ foreach ($attachments as $_adapter => $_att) {
+ if ($_adapter !== $attachment_adapter && Config::get('file.adapters.' . $_adapter . '.active') == true) {
+ echo HtmlBootstrap5::b('/file-admin/moveToAdapter?from_adapter=' . $attachment_adapter . '&to_adapter=' . $_adapter, 'Move all to ' . strtoupper($_adapter), 'Are you sure you want to move all attachments from ' . $attachment_adapter . ' to ' . $_adapter, null, false, 'secondary expand');
+ }
}
- }
- }?>
-
-
-
+ } ?>
+
+
+
-
No attachments available
-No attachments available
+Deleted files
-
+
- File | Path | Actions |
+
+ File |
+ Path |
+ Actions |
+
-
+
filename; ?> |
fullpath; ?> |
-
-
-
-
-
-
-
- id . "?redirect_url=" . urlencode("/file/deletedfiles"), "Restore file", "Are you sure you want to restore this file?"); ?>
- id . "?redirect_url=" . urlencode("/file/deletedfiles"), "Permanently Delete", "Are you sure you want to delete this file?"); ?>
+ | id, "View", null, null, true, "btn btn-sm btn-primary");
+ $action .= HtmlBootstrap5::b("/file-attachment/restore/" . $deleted_file->id . "?redirect_url=" . urlencode("/file/deletedfiles"), "Restore file", "Are you sure you want to restore this file?", null, false, "btn btn-sm btn-secondary");
+ $action .= HtmlBootstrap5::b("/file-attachment/delete/" . $deleted_file->id . "?redirect_url=" . urlencode("/file/deletedfiles"), "Permanently Delete", "Are you sure you want to delete this file?", null, false, "btn btn-sm btn-danger");
+ echo HtmlBootstrap5::buttonGroup($action); ?>
|
-
+
There are no soft deleted files
-Description
-
+