diff --git a/.composer-require-checker.config.json b/.composer-require-checker.config.json
index 17f8e78ad1..055f65cf83 100644
--- a/.composer-require-checker.config.json
+++ b/.composer-require-checker.config.json
@@ -19,7 +19,7 @@
"DB", "DBSlave",
"// GLPI base constants (they are not detected as they are dynamically declared)",
- "GLPI_AJAX_DASHBOARD", "GLPI_ALLOW_IFRAME_IN_RICH_TEXT", "GLPI_CALDAV_IMPORT_STATE", "GLPI_CACHE_DIR", "GLPI_CRON_DIR", "GLPI_CSRF_EXPIRES", "GLPI_CSRF_MAX_TOKENS", "GLPI_USE_IDOR_CHECK", "GLPI_IDOR_EXPIRES", "GLPI_DEMO_MODE", "GLPI_DOC_DIR", "GLPI_DUMP_DIR", "GLPI_FORCE_EMPTY_SQL_MODE", "GLPI_GRAPH_DIR", "GLPI_INSTALL_MODE", "GLPI_LOCAL_I18N_DIR", "GLPI_LOCK_DIR", "GLPI_LOG_DIR", "GLPI_NETWORK_REGISTRATION_API_URL", "GLPI_PICTURE_DIR", "GLPI_PLUGIN_DOC_DIR", "GLPI_RSS_DIR", "GLPI_SESSION_DIR", "GLPI_TELEMETRY_URI", "GLPI_TMP_DIR", "GLPI_UPLOAD_DIR", "GLPI_USE_CSRF_CHECK", "GLPI_USER_AGENT_EXTRA_COMMENTS", "GLPI_VAR_DIR",
+ "GLPI_ALLOW_IFRAME_IN_RICH_TEXT", "GLPI_CALDAV_IMPORT_STATE", "GLPI_CACHE_DIR", "GLPI_CRON_DIR", "GLPI_CSRF_EXPIRES", "GLPI_CSRF_MAX_TOKENS", "GLPI_USE_IDOR_CHECK", "GLPI_IDOR_EXPIRES", "GLPI_DEMO_MODE", "GLPI_DOC_DIR", "GLPI_DUMP_DIR", "GLPI_FORCE_EMPTY_SQL_MODE", "GLPI_GRAPH_DIR", "GLPI_INSTALL_MODE", "GLPI_LOCAL_I18N_DIR", "GLPI_LOCK_DIR", "GLPI_LOG_DIR", "GLPI_PICTURE_DIR", "GLPI_PLUGIN_DOC_DIR", "GLPI_RSS_DIR", "GLPI_SESSION_DIR", "GLPI_TELEMETRY_URI", "GLPI_TMP_DIR", "GLPI_UPLOAD_DIR", "GLPI_USE_CSRF_CHECK", "GLPI_USER_AGENT_EXTRA_COMMENTS", "GLPI_VAR_DIR",
"// GLPI optionnal constants",
"GLPI_FORCE_MAIL", "GLPI_LOG_LVL",
diff --git a/ajax/dashboard.php b/ajax/dashboard.php
deleted file mode 100644
index 0a77aaf888..0000000000
--- a/ajax/dashboard.php
+++ /dev/null
@@ -1,140 +0,0 @@
-.
- * ---------------------------------------------------------------------
- */
-
-include ('../inc/includes.php');
-
-use Glpi\Dashboard\Grid;
-
-if (!isset($_REQUEST["action"])) {
- exit;
-}
-
-if (!isset($_REQUEST['embed']) || !$_REQUEST['embed']) {
- Session::checkCentralAccess();
-
-} else if (!in_array($_REQUEST['action'], [
- 'get_dashboard_items',
- 'get_card',
- 'get_cards'
-])) {
- Html::displayRightError();
-}
-
-$dashboard = new Glpi\Dashboard\Dashboard($_REQUEST['dashboard'] ?? "");
-
-switch ($_POST['action'] ?? null) {
- case 'save_new_dashboard':
- echo $dashboard->saveNew(
- $_POST['title'] ?? "",
- $_POST['context'] ?? ""
- );
- exit;
-
- case 'save_items':
- $dashboard->saveitems($_POST['items'] ?? []);
- $dashboard->saveTitle($_POST['title'] ?? "");
- exit;
-
- case 'save_rights':
- echo $dashboard->saveRights($_POST['rights'] ?? []);
- exit;
-
- case 'delete_dashboard':
- echo $dashboard->delete(['key' => $_POST['dashboard']]);
- exit;
-
- case 'set_last_dashboard':
- $grid = new Grid($_POST['dashboard'] ?? "");
- echo $grid->setLastDashboard($_POST['page'], $_POST['dashboard']);
- exit;
-
- case 'clone_dashboard':
- $new_dashboard = $dashboard->cloneCurrent();
- echo json_encode($new_dashboard);
- exit;
-}
-
-$grid = new Grid($_REQUEST['dashboard'] ?? "");
-
-header("Content-Type: text/html; charset=UTF-8");
-switch ($_REQUEST['action']) {
- case 'add_new':
- $grid->displayAddDashboardForm();
- break;
-
- case 'edit_rights':
- $grid->displayEditRightsForm();
- break;
-
- case 'display_edit_widget':
- case 'display_add_widget':
- $grid->displayWidgetForm($_REQUEST);
- break;
-
- case 'display_embed_form':
- $grid->displayEmbedForm();
- break;
-
- case 'get_card':
- session_write_close();
- echo $grid->getCardHtml($_REQUEST['card_id'], $_REQUEST);
- break;
-
- case 'get_cards':
- session_write_close();
- header("Content-Type: application/json; charset=UTF-8");
- // Parse stringified JSON payload (Used to preserve integers)
- $request_data = array_merge($_REQUEST, json_decode($_UREQUEST['data'], true));
- unset($request_data['data']);
- $cards = $request_data['cards'];
- unset($request_data['cards']);
- $result = [];
- foreach ($cards as $card) {
- $result[$card['card_id']] = $grid->getCardHtml($card['card_id'], array_merge($request_data, $card));
- }
- echo json_encode($result);
- break;
-
- case 'display_add_filter':
- $grid->displayFilterForm($_REQUEST);
- break;
- case 'get_dashboard_filters':
- echo $grid->getFiltersSetHtml($_REQUEST['filters'] ?? []);
- break;
- case 'get_filter':
- echo $grid->getFilterHtml($_REQUEST['filter_id']);
- break;
-
- case 'get_dashboard_items':
- echo $grid->getGridItemsHtml(true, $_REQUEST['embed'] ?? false);
- break;
-}
diff --git a/css/dashboard.scss b/css/dashboard.scss
deleted file mode 100644
index 402fe84724..0000000000
--- a/css/dashboard.scss
+++ /dev/null
@@ -1,1305 +0,0 @@
-$item_margin: 3px;
-$toolbar_height: 40px;
-$filter_height: 55px;
-$break_phones: 700px;
-$break_tablet: 1400px;
-
-@mixin soft-shadow {
- box-shadow:
- 0 1px 3px rgba(0,0,0,0.12),
- 0 1px 2px rgba(0,0,0,0.24) !important;
-}
-
-@mixin custom-scroll-bar() {
- // firefox
- scrollbar-width: none;
-
- // chrome
- &::-webkit-scrollbar {
- height: 6px;
- width: 4px;
- }
-
- &::-webkit-scrollbar-thumb,
- &::-webkit-scrollbar-track,
- &::-webkit-scrollbar-corner {
- background-color: transparent;
- }
-
- &:hover {
- // firefox
- scrollbar-width: thin;
-
- // chrome
- &::-webkit-scrollbar-thumb {
- background-color: rgba(0, 0, 0, .3);
- }
-
- &::-webkit-scrollbar-track {
- background-color: rgba(0, 0, 0, .2);
- }
- }
-}
-
-// replicate ui-tabs display
-.default_dashboard {
- padding: 1em 1.4em;
- border: 1px solid #dddddd;
- background: #FFF;
- box-shadow: 0px 3px 1px #dfdfdf;
- border-radius: 3px;
- width: 90%;
- margin: auto;
-}
-
-.dashboard {
- font-family: Arial, Helvetica, sans-serif/*{ffDefault}*/; // for embed
- position: relative;
- padding-top: $toolbar_height;
-
- // reset font weight from other gridstack usage
- .grid-stack-item-content a {
- font-weight: normal;
- }
-
- &.mini {
- padding-top: 0;
- width: calc(80% + 28px);
- margin: 0 auto;
- margin-bottom: -20px;
- z-index: 1;
-
- & + .search_page {
- z-index: 2;
- }
-
- .card {
- padding: 0 3px;
- }
-
- .grid-guide {
- top: 0;
- }
-
- .main-icon {
- font-size: 1.5em;
- right: 3px;
- bottom: 3px;
- }
-
- .grid-stack .grid-stack-item.lock-bottom {
- min-height: 0;
- height: 0;
- }
- }
-
- &.embed {
- .glpi_logo {
- background: url(../pics/logos/logo-GLPI-100-black.png) no-repeat;
- width: 100px;
- height: 55px;
- position: absolute;
- top: 30px;
- left: 100px;
- }
-
- &.nightmode {
- .glpi_logo {
- background: url(../pics/logos/logo-GLPI-100-white.png) no-repeat;
- }
- }
- }
-
- &.fullscreen, &.embed {
- background: white;
- padding: 100px;
-
- &.nightmode {
- background: black;
- color: rgb(153, 153, 153);
-
- .toolbar {
- i.fas {
- color: #EEEEEE;
-
- &.active {
- background: #777;
- border: 2px inset #777;
- }
- }
-
- select.dashboard_select {
- color: rgb(187, 187, 187);
- background-color: black;
- border: 2px solid rgba(255, 255, 255, .3);
-
- &:hover {
- border: 2px solid rgba(255, 255, 255, .5);
- }
- }
- }
-
- .grid-stack .grid-stack-item .grid-stack-item-content .card {
- background-color: #777;
- color: rgba(255, 255, 255, 0.7);
- }
- }
-
- .toolbar {
- top: 50px;
- right: 50px;
-
- &.left-toolbar {
- top: 50px;
- left: 104px;
- }
-
- .fs-toggle {
- display: none;
- }
- }
- }
-
- &.edit-mode {
- .grid-guide {
- display: flex;
- flex-wrap: wrap;
-
- @media screen and (max-width: $break_phones) {
- display: none;
- }
- }
-
- .grid-stack-item-content {
- touch-action: none;
- }
-
- .grid-stack .grid-stack-item:hover {
- .grid-stack-item-content:hover {
- cursor: grab;
- }
-
- & > .controls {
- display: inline-block;
- }
-
- .card {
- border-color: currentColor
- }
- }
-
- .markdown .html_content {
- display: none;
- }
- .markdown textarea.markdown_content {
- display: block;
- }
-
- .g-chart .ct-chart {
- [data-clickable=true] {
- cursor: grab;
- }
- }
-
- .toolbar {
- .change-dashboard {
- display: none;
- }
-
- .edit-dashboard-properties {
- display: inline-block;
- }
- }
-
- .filters_toolbar {
- .filters {
- fieldset.filter {
- &.filled {
- margin-top: 0.5em;
- }
- border-color: #f0f0f0 !important;
-
- input, .flatpickr, .select2, .no-wrap {
- display: none;
- }
-
- .delete-filter {
- display: inline-block;
- }
-
- legend {
- display: inline-block;
- cursor: grab;
- }
-
- &:hover {
- border-color: #509ee3 !important;
- background: rgb(80,158,227, .3);
- }
- }
-
- .filter-placeholder {
- border: 2px dashed #509ee3;
- padding: 1em 1em 0.8em;
- margin-top: 1em;
- border-radius: 4px;
- height: 10px;
- width: 50px;
- }
- }
-
- .filters-control {
- border-color: #f0f0f0;
-
- .add-filter {
- display: inline-block;
- }
- }
- }
-
- .card.filter-impacted {
- border-color: #509ee3;
-
- &:before {
- background: rgb(80,158,227, .3);
- content: '';
- position: absolute;
- width: inherit;
- height: inherit;
- margin: -3px;
- }
- }
- }
-
- .toolbar {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 10;
-
- &.left-toolbar {
- top: 0;
- right: initial;
- left: 5px;
-
- i.fas {
- margin-left: 0;
- font-size: 1em;
- }
-
- &:hover {
- select.dashboard_select {
- border: 2px solid rgba(0, 0, 0, .5);
- }
- }
- }
-
- select.dashboard_select {
- padding: 5px;
- border: 2px solid rgba(0, 0, 0, .1);
- border-radius: 3px;
- min-width: 100px;
- font-weight: bold;
- color: rgb(70, 70, 70);
- }
-
- i.fas {
- font-size: 1.5em;
- cursor: pointer;
- padding: 5px;
- margin-left: 5px;
- color: rgba(0, 0, 0, .5);
- border: 2px solid transparent;
-
- @media screen and (max-width: $break_phones) {
- display: none;
- }
-
- &:hover {
- color: rgb(0, 0, 0);
- }
-
- &.active {
- background: #EEEEEE;
- border: 2px inset #EEEEEE;
- }
-
- &.fa-moon {
- display: none;
- }
- }
-
- .edit-dashboard-properties {
- display: none;
-
- input.dashboard-name:not(.submit):not([type=submit]):not([type=reset]):not([type=checkbox]):not([type=radio]):not(.select2-search__field) {
- padding: 5px;
- border: 2px solid rgba(0, 0, 0, .1);
- border-radius: 3px;
- min-width: 200px;
- font-weight: bold;
- color: rgb(70, 70, 70);
- resize: horizontal;
- }
-
- i.save-dashboard-name {
- font-size: 1.5em;
- padding: 1px;
- vertical-align: middle;
- }
-
- .display-message {
- display: none;
- font-weight: bold;
-
- &.success {
- color: rgb(82, 145, 82);
- }
-
- &.fail {
- color: rgb(145, 82, 82);
- }
- }
- }
- }
-
- .filters_toolbar {
- text-align: left;
- height: $filter_height;
- margin: 0 5px 5px 2px;
- box-sizing: border-box;
- padding-top: 0.5em;
-
- @media screen and (max-width: $break_phones) {
- height: inherit;
- }
-
- .filters {
- display: inline-flex;
- flex-wrap: wrap;
-
- fieldset.filter {
- border: 2px solid #f0f0f0;
- border-radius: 4px;
- padding: 0.5em 1em 0.4em 1em;
- box-sizing: border-box;
- text-align: center;
- margin-top: 0.5em;
-
- legend {
- display: none;
- color: #74838f;
- font-weight: bold;
- }
-
- input {
- border: none !important;
- cursor: pointer;
- font-size: 1.1em !important;
- font-weight: bold;
- }
-
- .flatpickr {
- position: relative;
-
- a[data-clear] {
- display: none;
- }
-
- &:after {
- font-weight: 400;
- content: "\f133";
- right: 5px;
- }
- }
-
- .select2 {
- position: relative;
- margin-left: -12px;
-
- & ~ a, & ~ span { // remove icons next select2
- display: none;
- }
-
- .select2-selection.select2-selection--single {
- border: none;
- }
-
- .select2-container {
- margin-right: 0;
- }
-
- .select2-selection__rendered {
- color: #74838f;
- font-weight: bold;
- font-size: 1.1em;
- line-height: 18px;
- }
-
- .select2-selection__arrow {
- display: none;
- }
-
- .select2-selection__clear {
- color: transparent;
- position: relative;
-
- &:after {
- position: absolute;
- font-family: "Font Awesome 5 Free";
- color: #222;
- font-weight: 900;
- content: "\f057";
- opacity: 0.7;
- font-size: 11px;
- }
-
- &:hover:after {
- opacity: 1;
- }
- }
-
- &:after {
- font-weight: 900;
- content: "\f02b";
- }
- }
-
- .flatpickr, .select2 {
- &::placeholder, .select2-selection__placeholder {
- color: #74838f;
- opacity: .7;
- }
-
- &:after {
- font-family: "Font Awesome 5 Free";
- color: #74838f;
- opacity: .7;
- font-size: 1.3em;
- pointer-events: none;
- position: absolute;
- right: 0;
- top: 1px
- }
- }
-
- .delete-filter {
- display: none;
- color: rgba(0, 0, 0, 0.5);
- cursor: pointer;
- }
-
- &.filled {
- border-color: #509ee3;
- margin-top: 0;
- padding-top: 0.25em;
-
- legend {
- display: inline-block;
- }
-
- input, .select2-selection__rendered {
- color: #509ee3 !important;
- }
-
- .flatpickr, .select2 {
- &:after {
- display: none;
- }
-
- a[data-clear] {
- display: inline-block;
- }
- }
- }
- }
- }
-
- .filters-control {
- border: 2px dashed transparent;
- border-radius: 4px;
- box-sizing: border-box;
- display: inline-block;
- vertical-align: top;
- margin-top: 1em;
-
- .add-filter {
- padding: 1em 1em 0.8em;
- display: none;
- color: rgba(0, 0, 0, 0.5);
- cursor: pointer;
-
- &:hover {
- color: rgb(0, 0, 0);
- }
-
- .no-filter, .add-filter-lbl {
- padding-left: 0.2em;
- font-style: italic;
- color: #74838f;
- font-family: Arial, Helvetica, sans-serif;
- font-weight: bold;
- }
- }
-
- &:hover {
- border-color: rgba(0, 0, 0, 0.5);
- }
- }
- }
-
- .grid-guide {
- display: none;
- position: absolute;
- top: calc(#{$toolbar_height} + #{$filter_height} + 5px);
- width: calc(100% + 1px);
- border: 1px solid #EEEEEE;
- border-width: 0 1px 1px 0;
- background-image:
- linear-gradient(to right, #EEEEEE 1px, transparent 1px),
- linear-gradient(to bottom, #EEEEEE 1px, transparent 1px);
-
- .cell-add {
- opacity: 0;
- z-index: 2;
- position: relative;
- cursor: pointer;
- border: 2px dashed #777;
- box-sizing: border-box;
-
- &:hover {
- opacity: 1;
- }
-
- &:after {
- content: "\f067";
- left: 40%;
- top: 40%;
- font-size: 1em;
- color: grey;
- font-family: 'Font Awesome\ 5 Free';
- position: absolute;
- font-weight: 900;
- }
- }
- }
-
- .grid-stack {
- &.grid-stack-one-column-mode {
- max-width: 100%;
-
- .grid-stack-item {
- margin-bottom: 2px;
- }
- }
-
- .grid-stack-item {
-
- &:hover {
- cursor: default;
- }
-
- &.lock-bottom {
- display: none;
- }
-
- &.ui-draggable-dragging {
- .grid-stack-item-content {
- box-shadow: none;
- }
- }
-
- & > .ui-resizable-se {
- bottom: 5px;
- right: 5px;
- }
-
- & > .controls {
- display: none;
- position: absolute;
- top: 5px;
- right: 10px;
- z-index: 11;
- cursor: pointer;
-
- i.fas, i.far {
- opacity: .6;
-
- &:hover {
- opacity: 1;
- }
- }
- }
-
- .placeholder-content {
- left: $item_margin;
- right: $item_margin;
- }
-
- .loading-card {
- height: 100%;
- border: 1px solid rgba($color: #000, $alpha: .05);
-
- .fa-spin {
- position: absolute;
- top: calc(50% - 16px);
- left: calc(50% - 16px);
- color: #222;
- }
- }
-
- .empty-card {
- height: calc(100% - 5px);
- margin: $item_margin;
- border-radius: 3px;
- box-sizing: border-box;
- border: 1px solid transparent;
-
- .fas {
- position: absolute;
- top: calc(50% - 16px);
- left: calc(50% - 16px);
- font-size: 2em;
- }
- }
-
- .card-error {
- border-color: rgba(100, 0, 0, .3);
- background: rgba(255, 0, 0, .1);
- color: rgba(100, 0, 0, .5);
- }
-
- .card-warning {
- border-color: rgba(105, 100, 32, 0.3);
- background: rgba(255, 238, 0, 0.178);
- color: rgba(105, 100, 32, 0.3);
- }
-
- .no-data {
- display: block;
- position: relative;
-
- div {
- position: absolute;
- top: 50%;
- text-align: center;
- width: 100%;
- }
- }
-
- .ui-resizable-se {
- background: none;
- text-indent: unset;
-
- &:before {
- content: "\f338";
- font-family: "Font Awesome 5 Free";
- font-weight: 900;
- font-size: 13px;
- position: absolute;
- bottom: -5px;
- right: 1px;
- width: 20px;
- height: 20px;
- opacity: .6;
- }
-
- &:hover {
- &:before {
- opacity: 1;
- }
- }
- }
-
- .grid-stack-item-content {
- left: $item_margin;
- right: $item_margin;
- cursor: default;
- touch-action: initial;
-
- .debug-card {
- z-index: 10;
- position: absolute;
- color: rgba(255, 0, 0, 0.5);
- font-size: 10px;
- bottom: 5px;
- left: 5px;
- }
- }
- }
- }
-
- .card {
- @include soft-shadow;
- text-align: left;
- box-sizing: border-box;
- padding: 5px;
- height: calc(100% - 5px);
- width: calc(100% - 5px);
- margin: 2px 0 0 2px;
- display: block;
- color: rgba(0, 0, 0, 0.7);
- border: 2px solid transparent;
- border-radius: 3px;
- position: relative;
- box-sizing: border-box;
- background-color: #DDD;
-
- img {
- max-width: 100%;
- }
- }
-
-
- .big-number {
- .main-icon {
- right: 5px;
- top: 5px;
- }
-
- .formatted-number {
- display: flex;
-
- .number, .suffix {
- font-size: 3em;
- font-weight: normal;
-
- @media screen and (max-width: $break_tablet) {
- font-size: 2em;
- }
- }
- }
-
- .label {
- max-width: calc(100%);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- .main-icon {
- font-size: 3em;
- position: absolute;
- right: 5px;
- bottom: 5px;
-
- @media screen and (min-width: $break_phones) and (max-width: $break_tablet) {
- font-size: 2em;
- }
- }
-
- .main-label {
- margin: 5px;
- font-size: 1.5em;
- font-weight: bold;
- display: block;
- max-width: calc(100% - 2.5em);
-
- @media screen and (min-width: $break_phones) and (max-width: $break_tablet) {
- font-size: 1.1em;
- }
-
- i {
- color: currentColor;
- }
- }
-
- .summary-numbers {
- display: flex;
- flex-direction: column;
-
- .scrollable {
- flex-grow: 1;
- display: flex;
-
- .table {
- flex-grow: 1;
- display: flex;
- text-align: center;
-
- .line {
- flex-grow: 1;
- display: flex;
- flex-direction: column-reverse;
- justify-content: space-evenly;
- flex-basis: 100%;
- color: #555555;
- position: relative;
- font-weight: normal;
-
- .content {
- //flex-grow: 1;
- display: flex;
-
- .formatted-number {
- flex-grow: 1;
- }
- }
-
- .label {
- font-size: 1.5em;
- }
-
- &:not(:first-child) {
- border-left: 1px solid rgba(125, 125, 125, .2);
- }
-
- &:hover {
- background-color: rgba(125, 125, 125, .1);
- cursor: pointer;
- border-radius: 3px;
- border-left-color: transparent;
-
- & + .line {
- border-left-color: transparent;
- }
- }
- }
- }
- }
- }
-
- .multiple-numbers {
- $number_size: 1.9em;
-
- .scrollable {
- overflow: auto;
- max-height: calc(100% - 35px);
- @include custom-scroll-bar();
- }
-
- .table {
- display: table;
- width: 100%;
-
- .line {
- display: table-row;
- color: currentColor;
-
- * {
- display: table-cell;
- }
-
- &:nth-child(odd) {
- background-color: rgba($color: #000, $alpha: .05)
- }
-
- &:hover {
- background-color: rgba($color: #000, $alpha: .2)
- }
-
- .content {
- font-size: $number_size;
- min-width: 30px;
- background-color: rgba($color: #000, $alpha: .1);
- padding: 10px;
- text-align: center;
- min-width: 40px;
- white-space: nowrap;
- width: 1%;
- }
-
- .label, .icon {
- position: inherit;
- font-size: inherit;
- padding: 10px 0 10px 10px;
- line-height: $number_size;
- }
-
- .icon {
- color: rgba(0, 0, 0, .5);
- }
- }
- }
-
- .main-label {
- position: absolute;
- left: 5px;
- bottom: 5px;
- }
- }
-
- .articles-list {
- .scrollable {
- overflow: auto;
- max-height: calc(100% - 35px);
- @include custom-scroll-bar();
- }
-
- .line {
- color: currentColor;
- margin: 5px;
- padding: 5px;
- font-weight: normal;
- position: relative;
-
- .label {
- display: block;
- font-weight: bold;
- font-size: 1.4em;
- margin: 5px 0 10px;
- }
-
- .content {
- display: block;
- margin-bottom: 3px;
- font-size: 1.2em;
- overflow: hidden;
- text-overflow: ellipsis;
-
- &.long_text {
- max-height: 80px;
- }
- }
-
- .date {
- float: right
- }
-
- a, i.fa-user {
- color: currentColor;
- }
-
- i.fa-user {
- font-size: 0.8em;
- }
- }
-
- .main-label {
- position: absolute;
- left: 5px;
- bottom: 5px;
- }
- }
-
- .g-chart {
- display: flex;
- flex-direction: column;
-
- .chart {
- flex: 1;
- min-height: 0; // force firefox to respect flex-shrink on svg
- }
-
- .main-label {
- height: 30px;
- }
-
- .ct-chart-bar:not(.ct-horizontal-bars),
- .ct-chart-line {
- .ct-circle + .ct-label {
- font-size: 0.75rem;
- line-height: 1;
- paint-order: stroke;
- stroke-width: 5px;
- }
- .ct-labels {
- .ct-label:not(.ct-vertical) {
- word-break: break-word;
- }
- }
- }
-
- .ct-chart-line {
- .ct-label.ct-horizontal.ct-end {
- transform: translateX(-40%);
- }
- }
-
- .ct-chart-donut {
- fill: none;
-
- .ct-label {
- text-anchor: middle;
- font-weight: bold;
-
- &.fade {
- opacity: .2;
- }
- }
- }
-
- .ct-horizontal-bars {
- .ct-label.ct-vertical.ct-start {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- display: block;
-
- &:before {
- content: "";
- display: inline-block;
- height: 100%;
- vertical-align: middle;
- }
- }
- }
-
- .ct-chart-bar {
- .ct-barlabel {
- font-weight: bold;
- }
- }
-
- &.gauge {
- .ct-chart {
- max-height: calc(100% - 45px); // prevent gauge to overflow
-
- .ct-series.mouseover {
- .ct-slice-pie, .ct-slice-donut-solid {
- transform: scale(1.02); // with gauge, scale 5% si too much (we have clipping)
- }
- }
- }
- }
-
- @keyframes slice-fill-opacity {
- to {
- fill-opacity: 1;
- }
- }
-
- .ct-chart-donut, .ct-chart-pie {
- .ct-series {
- .ct-slice-pie, .ct-slice-donut-solid {
- stroke-width: 1px;
- transform-origin: center center;
- //transition: transform 400ms ease-in-out;
- fill-opacity: 0;
- animation: slice-fill-opacity 1s ease-in-out forwards;
- }
-
- &.disable-animation {
- .ct-slice-pie, .ct-slice-donut-solid {
- animation: none;
- fill-opacity: 1;
- }
- }
-
- &.mouseover {
- .ct-slice-pie, .ct-slice-donut-solid {
- fill-opacity: 1;
- stroke-opacity: 1;
- stroke-width: 3px;
- }
- }
-
- &.notmouseover {
- .ct-slice-pie, .ct-slice-donut-solid {
- fill-opacity: .5;
- stroke-opacity: 0;
- }
- }
- }
- }
-
- .ct-chart {
- [data-clickable=true] {
- cursor: pointer;
- }
- }
-
- .ct-point:hover {
- stroke-width: 13px;
- }
-
- &.pie, &.line.multiple, &.bar.tab10 {
- @import 'chartist/palette_d3_tab_10';
- }
-
- &.bar.tab20 {
- @import 'chartist/palette_d3_cat_20';
- }
-
- .ct-legend {
- text-align: left;
- padding: 10px 0 10px 38px;
-
- li {
- position: relative;
- padding-left: 23px;
- margin-bottom: 3px;
- }
-
- li:before {
- width: 12px;
- height: 12px;
- position: absolute;
- left: 0;
- content: '';
- border: 3px solid transparent;
- border-radius: 2px;
- }
-
- li.inactive:before {
- background-color: transparent !important;
- }
- }
- }
-
- .markdown {
- $mdmargin_v: 15px;
- $editor_border: 1px solid rgba(0, 0, 0, 0.3);
- overflow-y: auto;
- @include custom-scroll-bar();
-
- textarea.markdown_content {
- display: none;
- width: 100%;
- height: calc(100% - #{$mdmargin_v} * 2);
- resize: none;
- padding: 5px;
- margin: $mdmargin_v 0;
- box-sizing: border-box;
- background-color: rgba(255, 255, 255, .9);
- border: 1px solid currentColor;
- border-radius: 1px;
- outline: none;
- font-family: monospace
- }
-
- ul {
- list-style-type: disc;
-
- li {
- margin-left: 1.5em;
- }
- }
-
- h1 {
- border-bottom: 2px solid currentColor
- }
- h2 {
- border-bottom: 1px solid currentColor
- }
-
- img {
- max-width: 100%;
- max-height: 100%;
- }
- }
-
- .search-table {
- .table-container {
- overflow: auto;
- max-height: calc(100% - 40px);
- margin-top: 10px;
- @include custom-scroll-bar();
-
- .tab_cadrehov {
- margin: 0;
- min-width: 100%;
- box-shadow: none;
- background-color: transparent;
- font-size: 13px;
-
- th {
- position: sticky;
- top: 0;
- z-index: 10;
- padding: 5px 5px;
- border-bottom: 0;
- color: inherit;
- font-size: inherit;
-
- &.order_ASC:before,
- &.order_DESC:before {
- color: inherit;
- }
- }
-
- td {
- border-bottom: 0;
- }
-
- .tab_bg_1 {
- background-color: rgba(0, 0, 0, .03);
- }
- .tab_bg_2 {
- background-color: transparent;
- }
-
- a {
- color: inherit;
- font-size: inherit !important;
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
- }
-
- .main-icon {
- font-size: 2em;
- right: 5px;
- bottom: 5px;
- }
-
- .main-label {
- a {
- font-size: inherit !important;
- color: inherit;
- }
- }
- }
-}
-
-.chartist-tooltip.dashboard-tooltip {
- $bg-color: rgba($color: #FFFFFF, $alpha: .65);
-
- background-color: $bg-color;
- @include soft-shadow;
-
- &:before {
- border-top-color: $bg-color;
- }
-}
-
-.embed_block {
- border: 1px dashed #a0a0a0;
- background-color: #e9e9e9;
-}
-
-.widgettype_field {
- .widget-list {
- max-width: 400px;
- }
-
- input[type=radio].widget-select {
- display: none;
-
- & + label {
- background: rgba($color: #000, $alpha: .05);
- margin: 5px 5px 0 0;
- border: 2px solid transparent;
- border-radius: 3px;
- display: none;
- text-align: center;
- font-weight: bold;
-
- &:hover {
- background: rgba($color: #000, $alpha: .1);
- border-color: rgba($color: #000, $alpha: .2);
- cursor: pointer;
- }
- }
-
- &:checked + label {
- background: rgba($color: #000, $alpha: .2);
- border-color: rgba($color: #000, $alpha: .3);
- position: relative;
-
- &:after {
- content: "\f00c";
- right: 5px;
- bottom: 3px;
- font-size: 1em;
- color: rgb(39, 39, 39);
- font-family: 'Font Awesome\ 5 Free';
- position: absolute;
- font-weight: 900;
- }
- }
- }
-}
diff --git a/front/central.php b/front/central.php
index 022330b638..764bff968e 100644
--- a/front/central.php
+++ b/front/central.php
@@ -32,21 +32,6 @@
include ('../inc/includes.php');
-if (!(isset($_GET["embed"])
- && isset($_GET["dashboard"]))) {
- Session::checkCentralAccess();
-}
-
-// embed (anonymous) dashboard
-if (isset($_GET["embed"]) && isset($_GET["dashboard"])) {
- $grid = new Glpi\Dashboard\Grid($_GET["dashboard"]);
- $dashboard = $grid->getDashboard();
- Html::popHeader($dashboard->getTitle(), $_SERVER['PHP_SELF'], false, 'central', 'central');
- echo $grid->embed($_REQUEST);
- Html::popFooter();
- exit;
-}
-
// Change profile system
if (isset($_POST['newprofile'])) {
if (isset($_SESSION["glpiprofiles"][$_POST['newprofile']])) {
diff --git a/front/dashboard_assets.php b/front/dashboard_assets.php
deleted file mode 100644
index be0391ba73..0000000000
--- a/front/dashboard_assets.php
+++ /dev/null
@@ -1,54 +0,0 @@
-.
- * ---------------------------------------------------------------------
- */
-
-/**
- * Filename was previously states.php
- * @since 0.84
- */
-
-include ('../inc/includes.php');
-
-
-Session::checkCentralAccess();
-$default = Glpi\Dashboard\Grid::getDefaultDashboardForMenu('assets');
-
-// Redirect to "/front/computer.php" if no dashboard found
-if ($default == "") {
- Html::redirect($CFG_GLPI["root_doc"] . "/front/computer.php");
-}
-
-Html::header(__('Assets Dashboard'), $_SERVER['PHP_SELF'], "assets", "dashboard");
-
-$dashboard = new Glpi\Dashboard\Grid($default);
-$dashboard->showDefault();
-
-Html::footer();
diff --git a/front/dashboard_helpdesk.php b/front/dashboard_helpdesk.php
deleted file mode 100644
index fb0f8a1554..0000000000
--- a/front/dashboard_helpdesk.php
+++ /dev/null
@@ -1,54 +0,0 @@
-.
- * ---------------------------------------------------------------------
- */
-
-/**
- * Filename was previously states.php
- * @since 0.84
- */
-
-include ('../inc/includes.php');
-
-
-Session::checkCentralAccess();
-$default = Glpi\Dashboard\Grid::getDefaultDashboardForMenu('helpdesk');
-
-// Redirect to "/front/ticket.php" if no dashboard found
-if ($default == "") {
- Html::redirect($CFG_GLPI["root_doc"] . "/front/ticket.php");
-}
-
-Html::header(__('Helpdesk Dashboard'), $_SERVER['PHP_SELF'], "helpdesk", "dashboard");
-
-$dashboard = new Glpi\Dashboard\Grid($default);
-$dashboard->showDefault();
-
-Html::footer();
diff --git a/front/ticket.php b/front/ticket.php
index 8bd747197d..44e69f7a21 100644
--- a/front/ticket.php
+++ b/front/ticket.php
@@ -48,11 +48,6 @@
echo Html::manageRefreshPage(false, $callback);
-if ($default = Glpi\Dashboard\Grid::getDefaultDashboardForMenu('mini_ticket', true)) {
- $dashboard = new Glpi\Dashboard\Grid($default, 33, 2, 'mini_core');
- $dashboard->show(true);
-}
-
Search::show('Ticket');
if (Session::getCurrentInterface() == "helpdesk") {
diff --git a/inc/based_config.php b/inc/based_config.php
index f814b606fa..de85dae014 100644
--- a/inc/based_config.php
+++ b/inc/based_config.php
@@ -84,7 +84,6 @@
'GLPI_USER_AGENT_EXTRA_COMMENTS' => '', // Extra comment to add to GLPI User-Agent
// Other constants
- 'GLPI_AJAX_DASHBOARD' => '1',
'GLPI_CALDAV_IMPORT_STATE' => 0, // external events created from a caldav client will take this state by default (0 = Planning::INFO)
'GLPI_DEMO_MODE' => '0',
// TODO GLPI_FORCE_EMPTY_SQL_MODE need to be set to 0 after review of all sql queries
diff --git a/inc/central.class.php b/inc/central.class.php
index 94bf074c1b..9acb779425 100644
--- a/inc/central.class.php
+++ b/inc/central.class.php
@@ -68,11 +68,6 @@ function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
4 => _n('RSS feed', 'RSS feeds', Session::getPluralNumber()),
];
- $grid = new Glpi\Dashboard\Grid('central');
- if ($grid->canViewOneDashboard()) {
- array_unshift($tabs, __('Dashboard'));
- }
-
return $tabs;
}
return '';
@@ -84,9 +79,6 @@ static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtem
if ($item->getType() == __CLASS__) {
switch ($tabnum) {
case 0 :
- $item->showGlobalDashboard();
- break;
-
case 1 :
$item->showMyView();
break;
@@ -107,19 +99,6 @@ static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtem
return true;
}
- public function showGlobalDashboard() {
- echo "
";
echo "";
diff --git a/inc/dashboard/dashboard.class.php b/inc/dashboard/dashboard.class.php
deleted file mode 100644
index 7227ec1f73..0000000000
--- a/inc/dashboard/dashboard.class.php
+++ /dev/null
@@ -1,490 +0,0 @@
-.
- * ---------------------------------------------------------------------
- */
-
-namespace Glpi\Dashboard;
-
-use Ramsey\Uuid\Uuid;
-
-if (!defined('GLPI_ROOT')) {
- die("Sorry. You can't access this file directly");
-}
-
-class Dashboard extends \CommonDBTM {
- protected $id = 0;
- protected $key = "";
- protected $title = "";
- protected $embed = false;
- protected $items = null;
- protected $rights = null;
-
- static $all_dashboards = [];
- static $rightname = 'dashboard';
-
-
- public function __construct(string $dashboard_key = "") {
- $this->key = $dashboard_key;
- }
-
-
- static function getIndexName() {
- return "key";
- }
-
-
- /**
- * Retrieve the current dashboard from the DB (or from cache)
- * with its rights and items
- *
- * @param bool $force if true, don't use cache
- *
- * @return int
- */
- public function load(bool $force = false): int {
- $loaded = true;
- if ($force
- || count($this->fields) == 0
- || $this->fields['id'] == 0
- || strlen($this->fields['name']) == 0) {
- $loaded = $this->getFromDB($this->key);
- }
-
- if ($loaded) {
- if ($force || $this->items === null) {
- $this->items = Item::getForDashboard($this->fields['id']);
- }
-
- if ($force || $this->rights === null) {
- $this->rights = Right::getForDashboard($this->fields['id']);
- }
- }
-
- return $this->fields['id'] ?? false;
- }
-
-
- public function getFromDB($ID) {
- global $DB;
-
- $iterator = $DB->request([
- 'FROM' => self::getTable(),
- 'WHERE' => [
- 'key' => $ID
- ],
- 'LIMIT' => 1
- ]);
- if (count($iterator) == 1) {
- $this->fields = $iterator->next();
- $this->key = $ID;
- $this->post_getFromDB();
- return true;
- } else if (count($iterator) > 1) {
- \Toolbox::logWarning(
- sprintf(
- 'getFromDB expects to get one result, %1$s found!',
- count($iterator)
- )
- );
- }
-
- return false;
- }
-
-
- /**
- * Return the title of the current dasbhoard
- *
- * @return string
- */
- public function getTitle(): string {
- $this->load();
- return $this->fields['name'] ?? "";
- }
-
- /**
- * Do we have the right to view the current dashboard
- *
- * @return bool
- */
- public function canViewCurrent(): bool {
- // check global (admin) right
- if (self::canView()) {
- return true;
- }
-
- $this->load();
-
- //check shared rights
- $rights = self::convertRights($this->rights ?? []);
- return self::checkRights($rights);
- }
-
-
- /**
- * Save the current dashboard instance to DB
- *
- * @param string $title label of the dasbhoard, will be suglified to have a corresponding key
- * @param string $context of the dasbhoard, filter the dasboard collection by a key
- * @param array $items cards for the dashboard
- * @param array $rights for the dasbhoard
- *
- * @return string
- */
- public function saveNew(
- string $title = "",
- string $context = "core",
- array $items = [],
- array $rights = []
- ): string {
- $this->fields['name'] = $title;
- $this->fields['context'] = $context;
- $this->key = \Toolbox::slugify($title);
- $this->items = $items;
- $this->rights = $rights;
-
- $this->save();
-
- return $this->key;
- }
-
-
- /**
- * Save current dashboard
- *
- * @param bool $skip_child skip saving rights and items
- *
- * @return void
- */
- public function save(bool $skip_child = false) {
- global $DB, $GLPI_CACHE;
-
- $DB->updateOrInsert(self::getTable(), [
- 'key' => $this->key,
- 'name' => $this->fields['name'],
- 'context' => $this->fields['context']
- ], [
- 'key' => $this->key
- ]);
-
- // reload dashboard
- $this->getFromDB($this->key);
-
- //save items
- if (!$skip_child && count($this->items) > 0) {
- $this->saveItems($this->items);
- }
-
- //save rights
- if (!$skip_child && count($this->rights) > 0) {
- $this->saveRights($this->rights);
- }
-
- // invalidate dashboard cache
- $cache_key = "dashboard_card_".$this->key;
- $GLPI_CACHE->delete($cache_key);
- }
-
-
- function cleanDBonPurge() {
- $this->deleteChildrenAndRelationsFromDb([
- Item::class,
- Right::class,
- ]);
- }
-
- /**
- * Save items in DB for the current dashboard
- *
- * @param array $items cards of the dashboard, contains:
- * - gridstack_id: unique id of the card in the grid, usually build like card_id.uuidv4
- * - card_id: key of array return by getAllDasboardCards
- * - x: position in grid
- * - y: position in grid
- * - width: size in grid
- * - height: size in grid
- * - card_options, sub array, depends on the card, contains at least a key color
- *
- * @return void
- */
- public function saveItems(array $items = []) {
- $this->load();
- $this->items = $items;
-
- $this->deleteChildrenAndRelationsFromDb([
- Item::class,
- ]);
-
- Item::addForDashboard($this->fields['id'], $items);
- }
-
- /**
- * Save title DB for the current dashboard
- *
- * @param string $title of the current dashboard
- *
- * @return void
- */
- public function saveTitle(string $title = "") {
- if (!strlen($title)) {
- return;
- }
-
- $this->load();
- $this->fields['name'] = $title;
- $this->save(true);
- }
-
-
- /**
- * Save rights (share) in DB for the current dashboard
- *
- * @param array $rights contains these data:
- * - 'users_id' => [items_id]
- * - 'groups_id' => [items_id]
- * - 'entities_id' => [items_id]
- * - 'profiles_id' => [items_id]
- *
- * @return void
- */
- public function saveRights(array $rights = []) {
- $this->load();
- $this->rights = $rights;
-
- $this->deleteChildrenAndRelationsFromDb([
- Right::class,
- ]);
-
- Right::addForDashboard($this->fields['id'], $rights);
- }
-
-
- /**
- * Clone current Dashboard.
- * (Clean gridstack_id-id in new one)
- *
- * @return array with [title, key]
- */
- function cloneCurrent(): array {
- $this->load();
-
- $this->fields['name'] = sprintf(__('Copy of %s'), $this->fields['name']);
- $this->key = \Toolbox::slugify($this->fields['name']);
-
- // replace gridstack_id (with uuid V4) in the copy, to avoid cache issue
- $this->items = array_map(function(array $item) {
- $item['gridstack_id'] = $item['card_id'].Uuid::uuid4();
-
- return $item;
- }, $this->items);
-
- // convert right to the good format
- $this->rights = self::convertRights($this->rights);
-
- $this->save();
-
- return [
- 'title' => $this->fields['name'],
- 'key' => $this->key
- ];
- }
-
-
- /**
- * Retrieve all dashboards and store them into a static var
- *
- * @param bool $force don't check dashboard are already loaded and force their load
- * @param bool $check_rights use to remove rights checking (use in embed)
- * @param string $context only dashboard for given context
- *
- * @return array dasboards
- */
- static function getAll(
- bool $force = false,
- bool $check_rights = true,
- string $context = 'core'
- ): array {
- global $DB;
-
- if (!$force && count(self::$all_dashboards) > 0) {
- return self::$all_dashboards;
- }
-
- // empty previous data
- self::$all_dashboards = [];
-
- $dashboard_criteria = [];
- if (strlen($context)) {
- $dashboard_criteria['context'] = $context;
- }
-
- $dashboards = iterator_to_array($DB->request(self::getTable(), ['WHERE' => $dashboard_criteria]));
- $items = iterator_to_array($DB->request(Item::getTable()));
- $rights = iterator_to_array($DB->request(Right::getTable()));
-
- foreach ($dashboards as $dashboard) {
- $key = $dashboard['key'];
- $id = $dashboard['id'];
-
- $d_rights = array_filter($rights, function($right_line) use($id) {
- return $right_line['dashboards_dashboards_id'] == $id;
- });
- if ($check_rights && !self::checkRights(self::convertRights($d_rights))) {
- continue;
- }
- $dashboard['rights'] = self::convertRights($d_rights);
-
- $d_items = array_filter($items, function($item) use($id) {
- return $item['dashboards_dashboards_id'] == $id;
- });
- $d_items = array_map(function($item) {
- $item['card_options'] = importArrayFromDB($item['card_options']);
- return $item;
- }, $d_items);
- $dashboard['items'] = $d_items;
-
- self::$all_dashboards[$key] = $dashboard;
- }
-
- return self::$all_dashboards;
- }
-
-
- /**
- * Convert right from DB entries to a array with type foreign keys.
- * Ex:
- * IN
- * [
- * [
- * 'itemtype' => 'Entity'
- * 'items_id' => yyy
- * ], [
- * ...
- * ],
- * ]
- *
- * OUT
- * [
- * 'entities_id' => [...]
- * 'profiles_id' => [...]
- * 'users_id' => [...]
- * 'groups_id' => [...]
- * ]
- *
- * @param array $raw_rights right from DB
- *
- * @return array converter rights
- */
- static function convertRights(array $raw_rights = []): array {
- $rights = [
- 'entities_id' => [],
- 'profiles_id' => [],
- 'users_id' => [],
- 'groups_id' => [],
- ];
- foreach ($raw_rights as $right_line) {
- $fk = getForeignKeyFieldForItemType($right_line['itemtype']);
- $rights[$fk][] = $right_line['items_id'];
- }
-
- return $rights;
- }
-
-
- /**
- * Check a current set of rights
- *
- * @param array $rights
- *
- * @return bool
- */
- static function checkRights(array $rights = []): bool {
- // check global (admin) right
- if (self::canView()) {
- return true;
- }
-
- $default_rights = [
- 'entities_id' => [],
- 'profiles_id' => [],
- 'users_id' => [],
- 'groups_id' => [],
- ];
- $rights = array_merge_recursive($default_rights, $rights);
-
- // check specific rights
- if (count(array_intersect($rights['entities_id'], $_SESSION['glpiactiveentities']))
- || count(array_intersect($rights['profiles_id'], array_keys($_SESSION['glpiprofiles'])))
- || in_array($_SESSION['glpiID'], $rights['users_id'])
- || count(array_intersect($rights['groups_id'], $_SESSION['glpigroups']))) {
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Import dashboards from a variable
-
- * @param string|array $import json or php array representing the dashboards collection
- * [
- * dashboard_key => [
- * 'title' => '...',
- * 'items' => [...],
- * 'rights' => [...],
- * ], [
- * ...
- * ]
- * ]
- *
- * @return bool
- */
- static function importFromJson($import = null) {
- if (!is_array($import)) {
- $import = json_decode($import, true);
- if (json_last_error() !== JSON_ERROR_NONE) {
- return false;
- }
- }
-
- foreach ($import as $key => $dashboard) {
- $dash_object = new self($key);
- $dash_object->saveNew(
- $dashboard['title'] ?? $key,
- $dashboard['context'] ?? "core",
- $dashboard['items'] ?? [],
- $dashboard['rights'] ?? []
- );
- }
-
- return true;
- }
-}
diff --git a/inc/dashboard/filter.class.php b/inc/dashboard/filter.class.php
deleted file mode 100644
index 3f24170d7d..0000000000
--- a/inc/dashboard/filter.class.php
+++ /dev/null
@@ -1,251 +0,0 @@
-.
- * ---------------------------------------------------------------------
- */
-
-namespace Glpi\Dashboard;
-
-use CommonGLPI;
-use ITILCategory;
-use RequestType;
-use Location;
-use Manufacturer;
-use Session;
-use Html;
-use Group;
-use Plugin;
-use User;
-
-if (!defined('GLPI_ROOT')) {
- die("Sorry. You can't access this file directly");
-}
-
-/**
- * Filter class
-**/
-class Filter extends CommonGLPI {
-
- /**
- * Return all available filters
- * Plugins can hooks on this functions to add their own filters
- *
- * @return array of filters
- */
- static function getAll(): array {
- $filters = [
- 'dates' => __("Creation date"),
- 'dates_mod' => __("Last update"),
- 'itilcategory' => ITILCategory::getTypeName(Session::getPluralNumber()),
- 'requesttype' => RequestType::getTypeName(Session::getPluralNumber()),
- 'location' => Location::getTypeName(Session::getPluralNumber()),
- 'manufacturer' => Manufacturer::getTypeName(Session::getPluralNumber()),
- 'group_tech' => __("Technician group"),
- 'user_tech' => __("Technician"),
- ];
-
- $more_filters = Plugin::doHookFunction("dashboard_filters");
- if (is_array($more_filters)) {
- $filters = array_merge($filters, $more_filters);
- }
-
- return $filters;
- }
-
- /**
- * Get HTML for a dates range filter
- *
- * @param string|array $values init the input with these values, will be a string if empty values
- * @param string $fieldname how is named the current date field
- * (used to specify creation date or last update)
- *
- * @return string
- */
- static function dates($values = "", string $fieldname = 'dates'): string {
- // string mean empty value
- if (is_string($values)) {
- $values = [];
- }
-
- $rand = mt_rand();
- $label = self::getAll()[$fieldname];
- $field = Html::showDateField('filter-dates', [
- 'value' => $values,
- 'rand' => $rand,
- 'range' => true,
- 'display' => false,
- 'calendar_btn' => false,
- 'placeholder' => $label,
- 'on_change' => "on_change_{$rand}(selectedDates, dateStr, instance)",
- ]);
-
- $js = << 0);
- }
-
- /**
- * Get HTML for a dates range filter. Same as date but for last update field
- *
- * @param string|array $values init the input with these values, will be a string if empty values
- *
- * @return string
- */
- static function dates_mod($values): string {
- return self::dates($values, "dates_mod");
- }
-
-
- static function itilcategory(string $value = ""): string {
- return self::dropdown($value, 'itilcategory', ItilCategory::class);
- }
-
- static function requesttype(string $value = ""): string {
- return self::dropdown($value, 'requesttype', RequestType::class);
- }
-
- static function location(string $value = ""): string {
- return self::dropdown($value, 'location', Location::class);
- }
-
- static function manufacturer(string $value = ""): string {
- return self::dropdown($value, 'manufacturer', Manufacturer::class);
- }
-
- static function group_tech(string $value = ""): string {
- return self::dropdown($value, 'group_tech', Group::class, ['toadd' => [-1 => __("My groups")]]);
- }
-
- static function user_tech(string $value = ""): string {
- return self::dropdown($value, 'user_tech', User::class, ['right' => 'own_ticket']);
- }
-
- static function dropdown(
- string $value = "",
- string $fieldname = "",
- string $itemtype = "",
- array $add_params = []
- ): string {
- $value = !empty($value) ? (int) $value : null;
- $rand = mt_rand();
- $label = self::getAll()[$fieldname];
- $field = $itemtype::dropdown([
- 'name' => $fieldname,
- 'value' => $value,
- 'rand' => $rand,
- 'display' => false,
- 'display_emptychoice' => false,
- 'emptylabel' => '',
- 'placeholder' => $label,
- 'on_change' => "on_change_{$rand}()",
- 'allowClear' => true,
- 'width' => ''
- ] + $add_params);
-
- $js = << 0)
- };
-
-JAVASCRIPT;
- $js = Html::scriptBlock($js);
-
- return $js.self::field($fieldname, $field, $label, $value > 0);
- }
-
- /**
- * Get generic HTML for a filter
- *
- * @param string $id system name of the filter (ex "dates")
- * @param string $field html of the filter
- * @param string $label displayed label for the filter
- * @param bool $filled
- *
- * @return string the html for the complete field
- */
- static function field(
- string $id = "",
- string $field = "",
- string $label = "",
- bool $filled = false
- ): string {
-
- $rand = mt_rand();
- $class = $filled ? "filled" : "";
- $html = <<
- $field
-
-
-
-HTML;
-
- $js = << 0) {
- $('#filter-{$rand}').addClass('filled');
- } else {
- $('#filter-{$rand}').removeClass('filled');
- }
-
- $(this).width((str_len + 1) * 8 );
- });
-
- $('#filter-{$rand}')
- .hover(function() {
- $('.dashboard .card.filter-{$id}').addClass('filter-impacted');
- }, function() {
- $('.dashboard .card.filter-{$id}').removeClass('filter-impacted');
- });
- });
-JAVASCRIPT;
- $js = Html::scriptBlock($js);
-
- return $html.$js;
- }
-}
diff --git a/inc/dashboard/grid.class.php b/inc/dashboard/grid.class.php
deleted file mode 100644
index 5d72f1c3c4..0000000000
--- a/inc/dashboard/grid.class.php
+++ /dev/null
@@ -1,1524 +0,0 @@
-.
- * ---------------------------------------------------------------------
- */
-
-namespace Glpi\Dashboard;
-
-use Ramsey\Uuid\Uuid;
-
-use Config;
-use CommonGLPI;
-use Dropdown;
-use DBConnection;
-use Entity;
-use Group;
-use Html;
-use Plugin;
-use Profile;
-use Session;
-use ShareDashboardDropdown;
-use Telemetry;
-use Toolbox;
-use User;
-
-if (!defined('GLPI_ROOT')) {
- die("Sorry. You can't access this file directly");
-}
-
-class Grid extends CommonGLPI {
- protected $cell_margin = 6;
- protected $grid_cols = 26;
- protected $grid_rows = 24;
- protected $current = "";
- protected $dashboard = null;
- protected $items = [];
-
- static $embed = false;
- static $context = '';
- static $all_dashboards = [];
-
- public function __construct(
- string $dashboard_key = "central",
- int $grid_cols = 26,
- int $grid_rows = 24,
- string $context = "core"
- ) {
-
- $this->current = $dashboard_key;
- $this->grid_cols = $grid_cols;
- $this->grid_rows = $grid_rows;
-
- $this->dashboard = new Dashboard($dashboard_key);
- self::$context = $context;
- }
-
-
- /**
- * Return the instance of current dasbhoard
- *
- * @return Dashboard
- */
- public function getDashboard() {
- return $this->dashboard;
- }
-
-
- /**
- * load all existing dashboards from DB into a static property for caching data
- *
- * @param bool $force, if false, don't use cache
- *
- * @return bool
- */
- static function loadAllDashboards(bool $force = true): bool {
- if (!is_array(self::$all_dashboards)
- || count(self::$all_dashboards) === 0
- || $force) {
- self::$all_dashboards = Dashboard::getAll($force, !self::$embed, self::$context);
- }
-
- return is_array(self::$all_dashboards);
- }
-
-
- /**
- * Init dashboards cards
- * A define.php constant (GLPI_AJAX_DASHBOARD) exists to control how the cards should be loaded
- * - if true: only the parent block will be initialized and the content will be load by ajax
- * pros: if a widget fails, only this one will crash
- * - else: load all html
- * pros: better perfs
- *
- * @return void
- */
- public function getCards() {
- self::loadAllDashboards();
-
- if (!isset(self::$all_dashboards[$this->current])
- || !isset(self::$all_dashboards[$this->current]['items'])) {
- self::$all_dashboards[$this->current] = [
- 'items' => []
- ];
- }
-
- foreach (self::$all_dashboards[$this->current]['items'] as $specs) {
- $card_id = $specs['card_id'] ?? $specs['gridstack_id'] ?? $specs['id'];
- $gridstack_id = $specs['gridstack_id'] ?? $specs['id'];
- $card_options = ($specs['card_options'] ?? []) + [
- 'card_id' => $card_id
- ];
-
- if (GLPI_AJAX_DASHBOARD) {
- $card_html = <<
-
-
-HTML;
- } else {
- $card_html = $this->getCardHtml($card_id, ['args' => $card_options]);
- }
-
- // manage cache
- $dashboard_key = $this->current;
- $footprint = sha1(serialize($card_options).
- ($_SESSION['glpiactiveentities_string'] ?? "").
- ($_SESSION['glpilanguage']));
- $cache_key = "dashboard_card_{$dashboard_key}_{$footprint}";
- $card_options['cache_key'] = $cache_key;
-
- $this->addGridItem(
- $card_html,
- $gridstack_id,
- $specs['x'] ?? -1,
- $specs['y'] ?? -1,
- $specs['width'] ?? 2,
- $specs['height'] ?? 2,
- $card_options
- );
- }
- }
-
-
- /**
- * Do we have the right to view at least one dashboard int the current collection
- *
- * @return bool
- */
- public function canViewCurrent(): bool {
- // check global (admin) right
- if (Dashboard::canView()) {
- return true;
- }
-
- return $this->dashboard->canViewCurrent();
- }
-
-
- /**
- * Do we have the right to view at least one dashboard in the current collection
- *
- * @return bool
- */
- static function canViewOneDashboard(): bool {
- // check global (admin) right
- if (Dashboard::canView()) {
- return true;
- }
-
- self::loadAllDashboards();
-
- return (count(self::$all_dashboards) > 0);
- }
-
-
- /**
- * Do we have the right to view the specified dashboard int the current collection
- *
- * @param string $key the dashboard to check
- *
- * @return bool
- */
- static function canViewSpecificicDashboard($key): bool {
- // check global (admin) right
- if (Dashboard::canView()) {
- return true;
- }
-
- self::loadAllDashboards();
-
- return isset(self::$all_dashboards[$key]);
- }
-
-
- /**
- * Display grid for the current dashboard
- *
- * @return void display html of the grid
- */
- public function show(bool $mini = false) {
- $rand = mt_rand();
-
- if (!self::$embed && !$this->dashboard->canViewCurrent()) {
- return;
- }
-
- self::loadAllDashboards();
-
- $this->restoreLastDashboard();
-
- if ($mini) {
- $this->cell_margin = 3;
- }
-
- $embed_str = self::$embed ? "true" : "false";
- $embed_class = self::$embed ? "embed" : "";
- $mini_class = $mini ? "mini" : "";
-
- $nb_dashboards = count(self::$all_dashboards);
-
- $can_view_all = Session::haveRight('dashboard', READ) || self::$embed;
- $can_create = Session::haveRight('dashboard', CREATE);
- $can_edit = Session::haveRight('dashboard', UPDATE) && $nb_dashboards;
- $can_purge = Session::haveRight('dashboard', PURGE) && $nb_dashboards;
- $can_clone = $can_create && $nb_dashboards;
-
- // prepare html for add controls
- $add_controls = "";
- for ($y = 0; $y < $this->grid_rows; $y++) {
- for ($x = 0; $x < $this->grid_cols; $x++) {
- $add_controls.= " ";
- }
- }
-
- // prepare all available cards
- $cards = $this->getAllDasboardCards();
- $cards_json = json_encode($cards);
-
- // prepare all available widgets
- $all_widgets = Widget::getAllTypes();
- $all_widgets_json = json_encode($all_widgets);
-
- // prepare labels
- $embed_label = __("Share or embed this dashboard");
- $delete_label = __("Delete this dashboard");
- $history_label = __("Toggle auto-refresh");
- $night_label = __("Toggle night mode");
- $fs_label = __("Toggle fullscreen");
- $clone_label = __("Clone this dashboard");
- $edit_label = __("Toggle edit mode");
- $add_filter_lbl = __("Add filter");
- $add_dash_label = __("Add a new dashboard");
- $save_label = _x('button', "Save");
-
- $gridstack_items = $this->getGridItemsHtml();
-
- $dropdown_dashboards = "";
- if ($nb_dashboards) {
- $dropdown_dashboards = self::dropdownDashboard("", [
- 'value' => $this->current,
- 'display' => false,
- 'class' => 'dashboard_select',
- 'can_view_all' => $can_view_all,
- 'noselect2' => true,
- ]);
- }
-
- $dashboard_title = $this->dashboard->getTitle();
-
- $l_tb_icons = "";
- $r_tb_icons = "";
- $rename = "";
- $left_toolbar = "";
- $grid_guide = "";
-
- if (!self::$embed) {
- if (!$mini && $can_create) {
- $l_tb_icons.= "";
- }
- if (!$mini && $can_clone) {
- $r_tb_icons.= "";
- }
- if (!$mini && $can_edit) {
- $r_tb_icons.= "";
- $rename = "
-
-
-
- ";
- }
- if (!$mini && $can_purge) {
- $r_tb_icons.= "";
- }
- if ($can_edit) {
- $r_tb_icons.= "";
- }
-
- if (!$mini) {
- $r_tb_icons.= "";
- }
-
- if (!$mini) {
- $left_toolbar = <<
-
- $dropdown_dashboards
- $l_tb_icons
-
- $rename
-
-HTML;
- }
-
- $grid_guide = <<
- $add_controls
-
-HTML;
- }
-
- $toolbars = <<
-
-
- $r_tb_icons
-
-HTML;
-
- $filters = "";
- if (!$mini) {
- $filters = <<
-
-
-
- {$add_filter_lbl}
-
-
-
-HTML;
- }
-
- $embed_watermark = "";
- if (self::$embed) {
- $embed_watermark = "";
- }
-
- // display the grid
- $html = <<
- $embed_watermark
- $toolbars
- $filters
- $grid_guide
-
- $gridstack_items
-
-
-HTML;
-
- $ajax_cards = GLPI_AJAX_DASHBOARD;
- $context = self::$context;
- $cache_key = sha1($_SESSION['glpiactiveentities_string '] ?? "");
-
- $js = <<current}',
- cols: {$this->grid_cols},
- rows: {$this->grid_rows},
- cell_margin: {$this->cell_margin},
- rand: '{$rand}',
- embed: {$embed_str},
- ajax_cards: {$ajax_cards},
- all_cards: {$cards_json},
- all_widgets: {$all_widgets_json},
- context: "{$context}",
- cache_key: "{$cache_key}",
- })
- });
-JAVASCRIPT;
- $js = Html::scriptBlock($js);
-
- echo $html.$js;
- }
-
-
- public function showDefault() {
- echo "";
- $this->show();
- echo " ";
- }
-
-
- /**
- * Show an embeded dashboard.
- * We must check token validity to avoid displaying dashboard to invalid users
- *
- * @param array $params contains theses keys:
- * - dashboard: the dashboard system name
- * - entities_id: entity to init in session
- * - is_recursive: do we need to display sub entities
- * - token: the token to check
- *
- * @return void (display)
- */
- public function embed(array $params = []) {
- $defaults = [
- 'dashboard' => '',
- 'entities_id' => 0,
- 'is_recursive' => 0,
- 'token' => ''
- ];
- $params = array_merge($defaults, $params);
-
- if (!self::checkToken($params)) {
- Html::displayRightError();
- exit;
- }
-
- self::$embed = true;
-
- // load minimal session
- $_SESSION["glpiactive_entity"] = $params['entities_id'];
- $_SESSION["glpiactive_entity_recursive"] = $params['is_recursive'];
- $_SESSION["glpiname"] = 'embed_dashboard';
- $_SESSION["glpigroups"] = [];
- if ($params['is_recursive']) {
- $entities = getSonsOf("glpi_entities", $params['entities_id']);
- } else {
- $entities = [$params['entities_id']];
- }
- $_SESSION['glpiactiveentities'] = $entities;
- $_SESSION['glpiactiveentities_string'] = "'".implode("', '", $entities)."'";
-
- // show embeded dashboard
- $this->show(true);
- }
-
- static function getToken(string $dasboard = "", int $entities_id = 0, int $is_recursive = 0): string {
- $seed = $dasboard.$entities_id.$is_recursive.Telemetry::getInstanceUuid();
- $uuid = Uuid::uuid5(Uuid::NAMESPACE_OID, $seed);
- $token = $uuid->toString();
-
- return $token;
- }
-
- /**
- * Check token variables (compare it to `dashboard`, `entities_id` and `is_recursive` paramater)
- *
- * @param array $params contains theses keys:
- * - dashboard: the dashboard system name
- * - entities_id: entity to init in session
- * - is_recursive: do we need to display sub entities
- * - token: the token to check
- *
- * @return bool
- */
- static function checkToken(array $params = []):bool {
- $defaults = [
- 'dashboard' => '',
- 'entities_id' => 0,
- 'is_recursive' => 0,
- 'token' => ''
- ];
- $params = array_merge($defaults, $params);
-
- $token = self::getToken(
- $params['dashboard'],
- $params['entities_id'],
- $params['is_recursive']
- );
-
- if ($token !== $params['token']) {
- return false;
- Html::displayRightError();
- exit;
- }
-
- return true;
- }
-
-
- /**
- * Return the html for all items for the current dashboard
- *
- * @param bool $with_lock if true, return also a locked bottom item (to fix grid height)
- *
- * @return string html of the grid items
- */
- public function getGridItemsHtml(bool $with_lock = true, bool $embed = false): string {
- if ($embed) {
- self::$embed = true;
- }
-
- $this->getCards();
-
- if ($with_lock) {
- $this->items[] = <<
-HTML;
- }
-
- // append all elements to insert them in html
- return implode("", $this->items);
- }
-
-
- /**
- * Add a new grid item
- *
- * @param string $html content of the card
- * @param string $gridstack_id unique id identifying the card (used in gridstack)
- * @param int $x position in the grid
- * @param int $y position in the grid
- * @param int $width size in the grid
- * @param int $height size in the grid
- * @param array $data_option aditional options passed to the widget, contains at least thses keys:
- * - string 'color'
- * @return void
- */
- public function addGridItem(
- string $html = "",
- string $gridstack_id = "",
- int $x = -1,
- int $y = -1,
- int $width = 2,
- int $height = 2,
- array $data_option = []) {
-
- // let grid-stack to autoposition item
- $autoposition = 'data-gs-auto-position="true"';
- $coordinates = '';
- if ((int) $x >= 0 && (int) $y >= 0) {
- $autoposition = "";
- $coordinates = "data-gs-x='$x' data-gs-y='$y'";
- }
-
- $color = $data_option['color'] ?? "#FFFFFF";
- $fg_color = Toolbox::getFgColor($color, 100);
-
- // add card options in data attribute
- $data_option_attr = "";
- if (count($data_option)) {
- $data_option_attr = "data-card-options='".json_encode($data_option, JSON_HEX_APOS)."'";
- }
-
- $refresh_label = __("Refresh this card");
- $edit_label = __("Edit this card");
- $delete_label = __("Delete this card");
-
- $this->items[] = <<
-
-
-
-
-
- {$html}
-
-HTML;
- }
-
-
- /**
- * Display a mini form fo adding a new dashboard
- *
- * @return void (display)
- */
- public function displayAddDashboardForm() {
- $rand = mt_rand();
-
- echo ""; // .card.display-widget-form
- }
-
-
- /**
- * Display mini configuration form to add or edit a widget
- *
- * @param array $params with these keys:
- * - int 'gridstack_id': unique identifier of the card
- * - int 'x': position in the grid
- * - int 'y: position in the grid
- * - int 'width': size in the grid
- * - int 'height': size in the grid
- * - string 'rand': unique identifier for the dom
- * - string 'action': [display_add_widget|display_edit_widget] current action for the form
- * - array 'card_options': aditionnal options for the card, contains at least:
- * - string 'card_id': identifier return by @see self::getAllDasboardCards
- * - string 'color'
- *
- * @return void
- */
- public function displayWidgetForm(array $params = []) {
- $gridstack_id = $params['gridstack_id'] ?? "";
- $old_id = $gridstack_id;
- $x = (int) ($params['x'] ?? 0);
- $y = (int) ($params['y'] ?? 0);
- $width = (int) ($params['width'] ?? 0);
- $height = (int) ($params['height'] ?? 0);
- $cardopt = $params['card_options'] ?? ['color' => "#FAFAFA"];
- $card_id = $cardopt['card_id'] ?? "";
- $widgettypes = Widget::getAllTypes();
- $widgettype = $cardopt['widgettype'] ?? "";
- $widget_def = $widgettypes[$widgettype] ?? [];
- $use_gradient = $cardopt['use_gradient'] ?? 0;
- $point_labels = $cardopt['point_labels'] ?? 0;
- $limit = $cardopt['limit'] ?? 7;
- $color = $cardopt['color'];
- $edit = $params['action'] === "display_edit_widget";
- $rand = $params['rand'] ?? mt_rand();
- $cards = $this->getAllDasboardCards();
- $card = $cards[$card_id] ?? [];
- // append card id to options
- if (!isset($cardopt['card_id'] )) {
- $cardopt['card_id'] = $card_id;
- }
-
- $list_cards = [];
- array_walk($cards, function($data, $index) use (&$list_cards) {
- $group = $data['group'] ?? __("others");
- $list_cards[$group][$index] = $data['label'] ?? $data['itemtype']::getTypeName();
- });
-
- echo ""; // .card.display-widget-form
- }
-
-
- /**
- * Display mini form to add filter to the current dashboard
- *
- * @param array $params default values for
- * - 'used' already used filters
- *
- * @return void
- */
- public function displayFilterForm(array $params = []) {
- $default_params = [
- 'used' => [],
- ];
- $params = array_merge($default_params, $params);
-
- $used = array_flip($params['used']);
- $list_filters = array_diff_key(Filter::getAll(), $used);
-
- $rand = mt_rand();
- echo ""; // form.card.display-filter-form
- }
-
-
- /**
- * Display a mini form for embedding current dashboard in another application.
- * Also, display a select for sharing current dashboard to another users/groups/entities/profiles
- *
- * @return void
- */
- public function displayEmbedForm() {
- global $CFG_GLPI;
-
- $entities_id = $_SESSION['glpiactive_entity'];
- $is_recursive = $_SESSION['glpiactive_entity_recursive'];
- $token = self::getToken($this->current, $entities_id, $is_recursive);
-
- $embed_url = $CFG_GLPI['url_base'].
- "/front/central.php?embed&dashboard=".$this->current.
- "&entities_id=$entities_id".
- "&is_recursive=$is_recursive".
- "&token=$token";
-
- echo " ";
- echo " ";
-
- $this->displayEditRightsForm();
- }
-
-
- /**
- * Display a mini form for sharing current dashboard to another users/groups/entities/profiles.
- *
- * @return void
- */
- public function displayEditRightsForm() {
- self::loadAllDashboards();
- $rand = mt_rand();
- $values = [];
-
- echo " |