diff --git a/.gitignore b/.gitignore index b7f965c4b..d07ffc1dd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ report.txt demosaved.csv src/assets/css/tainacan-embeds.css src/assets/css/tainacan-embeds.css.map +src/assets/css/tainacan-reports.css +src/assets/css/tainacan-reports.css.map .DS_Store src/.DS_Store src/assets/css/tainacan-gutenberg-block-dynamic-items-list.css diff --git a/compile-sass.sh b/compile-sass.sh index 2f7ef2022..18ea85b54 100644 --- a/compile-sass.sh +++ b/compile-sass.sh @@ -12,33 +12,36 @@ sass -E 'UTF-8' --cache-location .tmp/sass-cache-1 src/views/admin/scss/tainacan sass -E 'UTF-8' --cache-location .tmp/sass-cache-2 src/views/roles/tainacan-roles.scss:src/assets/css/tainacan-roles.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-3 src/views/media-component/media-component.scss:src/assets/css/media-component.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-4 src/views/gutenberg-blocks/tainacan-collections/collections-list/collections-list.scss:src/assets/css/tainacan-gutenberg-block-collections-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-3 src/views/reports/tainacan-reports.scss:src/assets/css/tainacan-reports.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-5 src/views/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-collections-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-4 src/views/media-component/media-component.scss:src/assets/css/media-component.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-6 src/views/gutenberg-blocks/tainacan-items/items-list/items-list.scss:src/assets/css/tainacan-gutenberg-block-items-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-5 src/views/gutenberg-blocks/tainacan-collections/collections-list/collections-list.scss:src/assets/css/tainacan-gutenberg-block-collections-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-7 src/views/gutenberg-blocks/tainacan-items/dynamic-items-list/dynamic-items-list.scss:src/assets/css/tainacan-gutenberg-block-dynamic-items-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-6 src/views/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-collections-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-8 src/views/gutenberg-blocks/tainacan-items/search-bar/search-bar.scss:src/assets/css/tainacan-gutenberg-block-search-bar.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-7 src/views/gutenberg-blocks/tainacan-items/items-list/items-list.scss:src/assets/css/tainacan-gutenberg-block-items-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-9 src/views/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-items-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-8 src/views/gutenberg-blocks/tainacan-items/dynamic-items-list/dynamic-items-list.scss:src/assets/css/tainacan-gutenberg-block-dynamic-items-list.css + +sass -E 'UTF-8' --cache-location .tmp/sass-cache-9 src/views/gutenberg-blocks/tainacan-items/search-bar/search-bar.scss:src/assets/css/tainacan-gutenberg-block-search-bar.css sass -E 'UTF-8' --cache-location .tmp/sass-cache-10 src/views/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-items-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-11 src/views/gutenberg-blocks/tainacan-terms/terms-list/terms-list.scss:src/assets/css/tainacan-gutenberg-block-terms-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-12 src/views/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-items-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-12 src/views/gutenberg-blocks/tainacan-facets/facets-list/facets-list.scss:src/assets/css/tainacan-gutenberg-block-facets-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-12 src/views/gutenberg-blocks/tainacan-terms/terms-list/terms-list.scss:src/assets/css/tainacan-gutenberg-block-terms-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-13 src/views/gutenberg-blocks/tainacan-terms/carousel-terms-list/carousel-terms-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-terms-list.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-13 src/views/gutenberg-blocks/tainacan-facets/facets-list/facets-list.scss:src/assets/css/tainacan-gutenberg-block-facets-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-14 src/views/gutenberg-blocks/tainacan-facets/faceted-search/faceted-search.scss:src/assets/css/tainacan-gutenberg-block-faceted-search.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-14 src/views/gutenberg-blocks/tainacan-terms/carousel-terms-list/carousel-terms-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-terms-list.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-15 src/views/gutenberg-blocks/tainacan-items/item-submission-form/item-submission-form.scss:src/assets/css/tainacan-gutenberg-block-item-submission-form.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-15 src/views/gutenberg-blocks/tainacan-facets/faceted-search/faceted-search.scss:src/assets/css/tainacan-gutenberg-block-faceted-search.css -sass -E 'UTF-8' --cache-location .tmp/sass-cache-16 src/views/gutenberg-blocks/gutenberg-blocks-style.scss:src/assets/css/tainacan-gutenberg-block-common-styles.css +sass -E 'UTF-8' --cache-location .tmp/sass-cache-16 src/views/gutenberg-blocks/tainacan-items/item-submission-form/item-submission-form.scss:src/assets/css/tainacan-gutenberg-block-item-submission-form.css + +sass -E 'UTF-8' --cache-location .tmp/sass-cache-17 src/views/gutenberg-blocks/gutenberg-blocks-style.scss:src/assets/css/tainacan-gutenberg-block-common-styles.css echo "Compilação do Sass Concluído!" exit 0 diff --git a/package-lock.json b/package-lock.json index fef3296c8..2a8101582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1665,6 +1665,19 @@ } } }, + "apexcharts": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.24.0.tgz", + "integrity": "sha512-iT6czJCIVrmAtrcO90MZTQCvC+xi6R6Acf0jNH/d40FVTtCfcqECuKIh5iAMyOTtgUb7+fQ8rbadH2bm1kbL9Q==", + "requires": { + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -2992,6 +3005,11 @@ } } }, + "countup.js": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.0.7.tgz", + "integrity": "sha512-FO0nQdvG1iQwHp28wdvkErxnNUSbdkzztqZ6YNHKLHydngD2tdiKEW8dFrqpahF3tj+Ma70h0vyYrCBzxlVWdg==" + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -10002,6 +10020,70 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, + "svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "requires": { + "svg.js": "^2.0.1" + } + }, + "svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=", + "requires": { + "svg.js": ">=2.3.x" + } + }, + "svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=", + "requires": { + "svg.js": "^2.2.5" + } + }, + "svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "requires": { + "svg.js": "^2.4.0" + } + }, + "svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "requires": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "dependencies": { + "svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "requires": { + "svg.js": "^2.2.5" + } + } + } + }, + "svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "requires": { + "svg.js": "^2.6.5" + } + }, "swiper": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/swiper/-/swiper-5.4.5.tgz", @@ -10718,6 +10800,11 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" }, + "vue-apexcharts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz", + "integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==" + }, "vue-awesome-swiper": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/vue-awesome-swiper/-/vue-awesome-swiper-4.1.1.tgz", @@ -10728,6 +10815,11 @@ "resolved": "https://registry.npmjs.org/vue-blurhash/-/vue-blurhash-0.1.4.tgz", "integrity": "sha512-B76GgfHXHkdmYgAfI2rZl3BgCMD9OxAgn4Jw2Ro0a8ZoAKa6gqTWUrTo5EGXOftm/EKuMYi1Cc+UcAvV0jnoRw==" }, + "vue-countup-v2": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vue-countup-v2/-/vue-countup-v2-4.0.0.tgz", + "integrity": "sha512-XjKeHo1ndRlJtXvHd6B1eWOpbrJDdNU3rdYZwVPv2YlUXbvthsBT4kms5Fc/mn9RdXPrMX2H/ktAAQtKjWFisw==" + }, "vue-eslint-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz", diff --git a/package.json b/package.json index 2275df159..9668e5286 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "build-prod": "cross-env NODE_ENV=production webpack --config webpack.prod.js --display-error-details --progress --hide-modules" }, "dependencies": { + "apexcharts": "^3.24.0", + "countup.js": "^2.0.7", "axios": "^0.21.1", "blurhash": "^1.1.3", "buefy": "^0.9.7", @@ -24,8 +26,10 @@ "t": "^0.5.1", "v-tooltip": "^2.0.3", "vue": "^2.6.11", + "vue-apexcharts": "^1.6.0", "vue-awesome-swiper": "^4.1.1", "vue-blurhash": "^0.1.4", + "vue-countup-v2": "^4.0.0", "vue-masonry-css": "^1.0.3", "vue-router": "^3.1.6", "vue-the-mask": "^0.11.1", diff --git a/src/assets/css/tainacan-reports.css b/src/assets/css/tainacan-reports.css new file mode 100644 index 000000000..a66b58d81 --- /dev/null +++ b/src/assets/css/tainacan-reports.css @@ -0,0 +1,96 @@ +#tainacan-reports-app { + padding: 10px 2px 10px 2px; + margin: 0; } + #tainacan-reports-app .tainacan-icon::before { + opacity: 0.0; } + #tainacan-reports-app a:hover { + cursor: pointer; } + #tainacan-reports-app .wp-heading-inline { + margin-bottom: 2rem; } + #tainacan-reports-app .columns { + max-width: 100%; + align-items: flex-start; + justify-content: center; } + #tainacan-reports-app .columns .column { + max-width: 100%; + padding: 0; + position: relative; + box-sizing: content-box; } + #tainacan-reports-app .columns .column .postbox { + margin: 0.75rem; } + #tainacan-reports-app .columns .column .box-last-cached-on { + position: absolute; + bottom: calc(1px + 0.75rem); + right: calc(1px + 0.75rem); + display: inline-block; + padding: 0px 10px; + background-color: var(--tainacan-block-gray2, #dbdbdb); + color: var(--tainacan-block-gray4, #555758); + font-size: 0.875em; + border-top-left-radius: 3px; + opacity: 0.0; + transition: opacity 0.3s ease; } + #tainacan-reports-app .columns .column .box-last-cached-on button { + border: none; + background: none; + cursor: pointer; } + #tainacan-reports-app .columns .column .box-last-cached-on button:hover { + color: var(--wp-admin-theme-color, #007cba); } + #tainacan-reports-app .columns .column .postbox:hover + .box-last-cached-on, + #tainacan-reports-app .columns .column .postbox:hover + .box-last-cached-on + .box-last-cached-on, + #tainacan-reports-app .columns .column .box-last-cached-on:hover { + opacity: 1.0; } + #tainacan-reports-app .postbox { + padding: 1.125rem 1.25rem; + margin-bottom: 0; + height: 100%; + min-height: 380px; + background-color: var(--tainacan-block-gray0, #f6f6f6); } + #tainacan-reports-app .postbox label { + font-weight: bold; + font-size: 0.875rem; } + #tainacan-reports-app .postbox .box-header { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; } + #tainacan-reports-app .postbox .box-header .box-header__item { + margin-bottom: 10px; + line-height: 2rem; } + #tainacan-reports-app .graph-mode-switch { + display: inline-block; } + #tainacan-reports-app .graph-mode-switch button { + border: none !important; + background: none !important; + box-shadow: none !important; + padding: 0; + cursor: pointer; } + #tainacan-reports-app .graph-mode-switch button.current { + color: var(--wp-admin-theme-color, #007cba); } + #tainacan-reports-app .tainacan-custom-tooltip { + padding: 0; + display: flex; + justify-content: center; + align-items: flex-start; + flex-direction: column; } + #tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__header { + background-color: var(--tainacan-block-gray1, #f2f2f2); + display: flex; + justify-content: flex-start; + align-items: center; + width: 100%; + padding: 6px 10px 4px 10px; } + #tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__header + .tainacan-custom-tooltip__body { + padding: 4px 10px 6px 10px; } + #tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__body { + width: 100%; + padding: 6px 10px; + display: flex; + justify-content: center; + align-items: flex-start; + flex-direction: column; } + #tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__body p { + margin-bottom: 4px; + font-size: 0.85rem; } + +/*# sourceMappingURL=tainacan-reports.css.map */ diff --git a/src/assets/css/tainacan-reports.css.map b/src/assets/css/tainacan-reports.css.map new file mode 100644 index 000000000..5666c19b6 --- /dev/null +++ b/src/assets/css/tainacan-reports.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAAA,qBAAsB;EAClB,OAAO,EAAE,iBAAiB;EAC1B,MAAM,EAAE,CAAC;EAGT,4CAAuB;IACnB,OAAO,EAAE,GAAG;EAGhB,6BAAQ;IACJ,MAAM,EAAE,OAAO;EAGnB,wCAAmB;IACf,aAAa,EAAE,IAAI;EAGvB,8BAAS;IACL,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,UAAU;IACvB,eAAe,EAAE,MAAM;IAEvB,sCAAQ;MACJ,SAAS,EAAE,IAAI;MACf,OAAO,EAAE,CAAC;MACV,QAAQ,EAAE,QAAQ;MAClB,UAAU,EAAE,WAAW;MAEvB,+CAAS;QACL,MAAM,EAAE,OAAO;MAEnB,0DAAoB;QAChB,QAAQ,EAAE,QAAQ;QAClB,MAAM,EAAE,mBAAmB;QAC3B,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE,YAAY;QACrB,OAAO,EAAE,QAAQ;QACjB,gBAAgB,EAAE,oCAAoC;QACtD,KAAK,EAAE,oCAAoC;QAC3C,SAAS,EAAE,OAAO;QAClB,sBAAsB,EAAE,GAAG;QAC3B,OAAO,EAAE,GAAG;QACZ,UAAU,EAAE,iBAAiB;QAE7B,iEAAO;UACH,MAAM,EAAE,IAAI;UACZ,UAAU,EAAE,IAAI;UAChB,MAAM,EAAE,OAAO;UAEf,uEAAQ;YACJ,KAAK,EAAE,oCAAoC;MAIvD;;sEAE0B;QACtB,OAAO,EAAE,GAAG;EAKxB,8BAAS;IACL,OAAO,EAAE,gBAAgB;IACzB,aAAa,EAAE,CAAC;IAChB,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,KAAK;IACjB,gBAAgB,EAAE,oCAAoC;IAEtD,oCAAM;MACF,WAAW,EAAE,IAAI;MACjB,SAAS,EAAE,QAAQ;IAGvB,0CAAY;MACR,OAAO,EAAE,IAAI;MACb,WAAW,EAAE,QAAQ;MACrB,eAAe,EAAE,aAAa;MAC9B,SAAS,EAAE,IAAI;MAEf,4DAAkB;QACd,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,IAAI;EAI7B,wCAAmB;IACf,OAAO,EAAE,YAAY;IACrB,+CAAO;MACH,MAAM,EAAE,eAAe;MACvB,UAAU,EAAE,eAAe;MAC3B,UAAU,EAAE,eAAe;MAC3B,OAAO,EAAE,CAAC;MACV,MAAM,EAAE,OAAO;MAEf,uDAAU;QACN,KAAK,EAAE,oCAAoC;EAKvD,8CAAyB;IACrB,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,MAAM;IACvB,WAAW,EAAE,UAAU;IACvB,cAAc,EAAE,MAAM;IAEtB,+EAAiC;MAC7B,gBAAgB,EAAE,oCAAoC;MACtD,OAAO,EAAE,IAAI;MACb,eAAe,EAAE,UAAU;MAC3B,WAAW,EAAE,MAAM;MACnB,KAAK,EAAE,IAAI;MACX,OAAO,EAAE,iBAAiB;IAG9B,gHAAgE;MAC5D,OAAO,EAAE,iBAAiB;IAE9B,6EAA+B;MAC3B,KAAK,EAAE,IAAI;MACX,OAAO,EAAE,QAAQ;MACjB,OAAO,EAAE,IAAI;MACb,eAAe,EAAE,MAAM;MACvB,WAAW,EAAE,UAAU;MACvB,cAAc,EAAE,MAAM;MAEtB,+EAAE;QACE,aAAa,EAAE,GAAG;QAClB,SAAS,EAAE,OAAO", +"sources": ["../../views/reports/tainacan-reports.scss"], +"names": [], +"file": "tainacan-reports.css" +} diff --git a/src/classes/api/endpoints/class-tainacan-rest-reports-controller.php b/src/classes/api/endpoints/class-tainacan-rest-reports-controller.php new file mode 100644 index 000000000..df40371d3 --- /dev/null +++ b/src/classes/api/endpoints/class-tainacan-rest-reports-controller.php @@ -0,0 +1,837 @@ +rest_base = 'reports'; + parent::__construct(); + add_action('init', array(&$this, 'init_objects'), 11); + } + + public function init_objects() { + $this->items_repository = Repositories\Items::get_instance(); + $this->taxonomy_repository = Repositories\Taxonomies::get_instance(); + $this->metadatum_repository = Repositories\Metadata::get_instance(); + $this->collections_repository = Repositories\Collections::get_instance(); + } + + /** + * + * + * @throws \Exception + */ + public function register_routes() { + register_rest_route($this->namespace, $this->rest_base . '/collection', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_collections'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/collection/(?P[\d]+)/summary', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_summary'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/collection/(?P[\d]+)/metadata', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_stats_collection_metadata'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/metadata', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_stats_collection_metadata'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/repository/summary', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_summary'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/taxonomy', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_taxonomies_list'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/taxonomy/(?P[\d]+)', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_taxonomy'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/activities', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_activities'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'start' => [ + 'title' => __( 'start Date', 'tainacan' ), + 'type' => 'string', + 'format' => 'date-time', + ], + 'end' => [ + 'title' => __( 'start Date', 'tainacan' ), + 'type' => 'string', + 'format' => 'date-time', // RFC3339. https://tools.ietf.org/html/rfc3339#section-5.8 + ], + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/collection/(?P[\d]+)/activities', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_activities'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'start' => [ + 'title' => __( 'start Date', 'tainacan' ), + 'type' => 'string', + 'format' => 'date-time', + ], + 'end' => [ + 'title' => __( 'start Date', 'tainacan' ), + 'type' => 'string', + 'format' => 'date-time', // RFC3339. https://tools.ietf.org/html/rfc3339#section-5.8 + ], + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + register_rest_route($this->namespace, $this->rest_base . '/collection/(?P[\d]+)/metadata/(?P[\d]+)', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array($this, 'get_metadata'), + 'permission_callback' => array($this, 'reports_permissions_check'), + 'args' => [ + 'parent' => [ + 'title' => __( 'parent', 'tainacan' ), + 'type' => 'integer', + ], + 'force' => [ + 'title' => __( 'Force regenerete', 'tainacan' ), + 'type' => 'string', + 'default' => 'no', + 'description' => __( 'Force generates the reports graphic.', 'tainacan' ), + 'enum' => array( + 'no', + 'yes' + ) + ] + ] + ), + ) + ); + } + + public function reports_permissions_check($request) { + return true; + } + + public function get_collections($request) { + $response = array( + 'list' => [] + ); + $key_cache_object = 'collections'; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + $collections = $this->collections_repository->fetch([]); + + if($collections->have_posts()) { + while ($collections->have_posts()) { + $collections->the_post(); + $collection = new Entities\Collection($collections->post); + $total_items = wp_count_posts( $collection->get_db_identifier(), 'readable' ); + + if (isset($total_items->publish) || isset($total_items->private) || + isset($total_items->trash) || isset($total_items->draft)) { + $response['list'][$collection->get_db_identifier()] = array( + 'id' => $collection->get_id(), + 'name' => $collection->get_name(), + 'items' => array( + 'trash' => intval($total_items->trash), + 'draft' => intval($total_items->draft), + 'publish' => intval($total_items->publish), + 'private' => intval($total_items->private), + 'total' => $total_items->trash + $total_items->draft + $total_items->publish + $total_items->private + ) + ); + } + } + wp_reset_postdata(); + } + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + public function get_summary($request) { + $response = array( + 'totals'=> array( + 'items' => array( + 'total' => 0, + 'trash' => 0, + 'draft' => 0, + 'publish' => 0, + 'private' => 0 + ) + ) + ); + if(isset($request['collection_id'])) { + $collection_id = $request['collection_id']; + + $key_cache_object = 'summary_' . $collection_id; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $collection = $this->collections_repository->fetch($collection_id); + $total_items = wp_count_posts( $collection->get_db_identifier(), 'readable' ); + if (isset($total_items->publish) || + isset($total_items->private) || + isset($total_items->trash) || + isset($total_items->draft)) { + + $response['totals']['items']['trash'] = intval($total_items->trash); + $response['totals']['items']['draft'] = intval($total_items->draft); + $response['totals']['items']['publish'] = intval($total_items->publish); + $response['totals']['items']['private'] = intval($total_items->private); + } + } else { + $key_cache_object = 'summary'; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $collections = $this->collections_repository->fetch([]); + $response['totals']['collections'] = array( + 'total' => 0, + 'trash' => 0, + 'publish' => 0, + 'private' => 0 + ); + if($collections->have_posts()) { + while ($collections->have_posts()) { + $collections->the_post(); + $collection = new Entities\Collection($collections->post); + $response['totals']['collections'][$collection->get_status()]++; + $response['totals']['collections']['total']++; + $total_items = wp_count_posts( $collection->get_db_identifier(), 'readable' ); + + if (isset($total_items->publish) || isset($total_items->private) || + isset($total_items->trash) || isset($total_items->draft)) { + $response['totals']['items']['trash'] += $total_items->trash; + $response['totals']['items']['draft'] += $total_items->draft; + $response['totals']['items']['publish'] += $total_items->publish; + $response['totals']['items']['private'] += $total_items->private; + } + } + wp_reset_postdata(); + } + + $response['totals']['taxonomies'] = array( + 'total' => 0, + 'trash' => 0, + 'publish' => 0, + 'draft' => 0, + 'private' => 0, + 'used' => 0, + 'not_used'=> 0 + ); + $total_taxonomies = wp_count_posts( 'tainacan-taxonomy', 'readable' ); + + if (isset($total_taxonomies->publish) || + isset($total_taxonomies->private) || + isset($total_taxonomies->trash) || + isset($total_taxonomies->draft)) { + + $response['totals']['taxonomies']['trash'] = intval($total_taxonomies->trash); + $response['totals']['taxonomies']['publish'] = intval($total_taxonomies->publish); + $response['totals']['taxonomies']['draft'] = intval($total_taxonomies->draft); + $response['totals']['taxonomies']['private'] = intval($total_taxonomies->private); + $response['totals']['taxonomies']['total'] = $response['totals']['taxonomies']['trash'] + $response['totals']['taxonomies']['publish'] + $response['totals']['taxonomies']['draft'] + $response['totals']['taxonomies']['private']; + $response['totals']['taxonomies']['used'] = $this->query_count_used_taxononomies(); + $response['totals']['taxonomies']['not_used'] = $response['totals']['taxonomies']['total'] - $response['totals']['taxonomies']['used']; + } + } + $response['totals']['items']['total'] = ($response['totals']['items']['trash'] + $response['totals']['items']['draft'] + $response['totals']['items']['publish'] + $response['totals']['items']['private']); + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + public function get_taxonomies_list($request) { + $response = array( + 'list'=> array( + + ) + ); + + $key_cache_object = 'taxonomies_list'; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $taxonomies = $this->taxonomy_repository->fetch(); + if($taxonomies->have_posts()){ + while ($taxonomies->have_posts()){ + $taxonomies->the_post(); + + $taxonomy = new Entities\Taxonomy($taxonomies->post); + $total_terms = intval(wp_count_terms( $taxonomy->get_db_identifier(), array('hide_empty'=> false) )); + $total_terms_used = intval(wp_count_terms( $taxonomy->get_db_identifier(), array('hide_empty'=> true) )); + $total_terms_not_used = $total_terms - $total_terms_used; + + $response['list'][$taxonomy->get_db_identifier()] = array( + 'id' => $taxonomy->get_id(), + 'name' => $taxonomy->get_name(), + 'total_terms' => $total_terms, + 'total_terms_used' => $total_terms_used, + 'total_terms_not_used' => $total_terms_not_used + ); + } + wp_reset_postdata(); + } + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + public function get_taxonomy($request) { + $response = array( + 'terms'=> array() + ); + $taxonomy_id = $request['taxonomy_id']; + $taxonomy = $this->taxonomy_repository->fetch($taxonomy_id); + $taxonomy_identifier = $taxonomy->get_db_identifier(); + $taxonomy_total_terms = wp_count_terms($taxonomy_identifier, array('hide_empty' => false) ); + $limit = 100; + $offset = 0; + + if ( !$taxonomy_total_terms) { + $taxonomy_total_terms = 0; + } else { + $key_cache_object = 'taxonomy_' . $taxonomy_identifier; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + } + + while($offset < $taxonomy_total_terms) { + $terms = get_terms( array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'number' => $limit, + 'offset' => $offset, + 'hide_empty' => false, + ) ); + foreach ($terms as $term) { + $response['terms'][$term->term_id] = array( + 'id' => $term->term_id, + 'name' => $term->name, + 'count' => $term->count + ); + } + $offset+=$limit; + } + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + public function get_metadata($request) { + $response = array( + 'list' => array() + ); + $collection_id = $request['collection_id']; + $taxonomy_metadata_id = $request['metadata_id'];; + $parent_id = 0; + if ( isset($request['parent']) ) { + $parent_id = (int) $request['parent']; + } + + $key_cache_object = "facet_taxonomy_$taxonomy_metadata_id-$collection_id-$parent_id"; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $metadatum = $this->metadatum_repository->fetch($taxonomy_metadata_id); + $metadatum_type = $metadatum->get_metadata_type(); + if($metadatum_type != 'Tainacan\Metadata_Types\Taxonomy') { + return new \WP_REST_Response([ + 'error_message' => __('Only taxonomy metadata type has allowed.', 'tainacan'), + 'metadatum_type' => $metadatum_type + ], 400); + } + + $args = [ + 'collection_id' => $collection_id, + 'parent_id' => $parent_id, + 'count_items'=> true, + ]; + $data = $this->metadatum_repository->fetch_all_metadatum_values($taxonomy_metadata_id, $args); + $response['list'] = array_map(function($item) { + return [ + 'type' => $item['type'], + 'value' => $item['value'], + 'label' => $item['label'], + 'parent' => $item['parent'] == null ? 0 : $item['parent'], + 'total_items' => intval($item['total_items']), + 'total_children' => intval($item['total_children']), + ]; + }, $data['values']); + + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + public function get_stats_collection_metadata($request) { + $response = array( + 'totals'=> array( + 'metadata' => array( + 'total' => 0, + 'publish' => 0, + 'private' => 0 + ), + 'metadata_per_type' => array() + ), + 'distribution' => array( + + ) + ); + + if(isset($request['collection_id'])) { + $collection_id = $request['collection_id']; + + $key_cache_object = 'stats_collection_metadata_' . $collection_id; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $collection = new Entities\Collection( $collection_id ); + $result_metadatum = $this->metadatum_repository->fetch_by_collection( $collection, [] ); + $response['totals']['metadata']['total'] = count($result_metadatum); + $meta_ids=[]; + foreach($result_metadatum as $metadatum) { + $meta_type = explode('\\', $metadatum->get_metadata_type()) ; + $meta_type = strtolower($meta_type[sizeof($meta_type)-1]); + $meta_type_name = $metadatum->get_metadata_type_object()->get_name(); + if( in_array($meta_type, ['core_description','core_title']) ) { + $meta_type = 'text'; + $meta_type_name = (new \Tainacan\Metadata_Types\Text())->get_name(); + } + + $response['totals']['metadata'][$metadatum->get_status()]++; + $response['totals']['metadata_per_type'][$meta_type]['name'] = $meta_type_name; + $response['totals']['metadata_per_type'][$meta_type]['count']++; + + $meta_ids[] = $metadatum->get_id(); + } + $response['distribution'] = $this->query_item_metadata_distribution($meta_ids, $collection->get_db_identifier()); + //wp_count_posts() + } else { + $args = [ + 'meta_query' => [ + [ + 'key' => 'collection_id', + 'value' => 'default', + 'compare' => '=' + ] + ] + ]; + + $key_cache_object = 'stats_collection_metadata'; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $result_metadatum = $this->metadatum_repository->fetch( $args, 'OBJECT' ); + $meta_ids=[]; + foreach($result_metadatum as $metadatum) { + $meta_type = explode('\\', $metadatum->get_metadata_type()) ; + $meta_type = strtolower($meta_type[sizeof($meta_type)-1]); + $meta_type_name = $metadatum->get_metadata_type_object()->get_name(); + if( in_array($meta_type, ['core_description','core_title']) ) { + $meta_type = 'text'; + $meta_type_name = (new \Tainacan\Metadata_Types\Text())->get_name(); + } + + $response['totals']['metadata'][$metadatum->get_status()]++; + $response['totals']['metadata_per_type'][$meta_type]['name'] = $meta_type_name; + $response['totals']['metadata_per_type'][$meta_type]['count']++; + + $meta_ids[] = $metadatum->get_id(); + } + $response['distribution'] = $this->query_item_metadata_distribution($meta_ids, 'default'); + + } + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + private function query_item_metadata_distribution($meta_ids, $collection_post_type) { + $count_posts = wp_count_posts( $collection_post_type, 'readable' ); + $total_items = intval($count_posts->trash) + intval($count_posts->draft) + + intval($count_posts->publish) + intval($count_posts->private); + + global $wpdb; + $string_meta_ids = "'".implode("','", $meta_ids)."'"; + $sql_statement = $wpdb->prepare( + "SELECT p.post_title AS 'name', p.id AS id, IFNULL(((m.total/$total_items) * 100), 0) as fill_percentage + FROM + $wpdb->posts p LEFT JOIN + ( + SELECT meta_key, count(DISTINCT post_id) AS total + FROM $wpdb->postmeta + WHERE $wpdb->postmeta.meta_key IN ($string_meta_ids) + AND $wpdb->postmeta.post_id IN ( + SELECT id + FROM $wpdb->posts + WHERE $wpdb->posts.post_type = '$collection_post_type' + ) + GROUP BY $wpdb->postmeta.meta_key + UNION + SELECT mt.meta_key, count(DISTINCT tr.object_id) AS total + FROM $wpdb->term_taxonomy tt + INNER JOIN $wpdb->term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id + INNER JOIN ( + SELECT post_id as 'meta_key', concat('tnc_tax_', meta_value) as 'tax_id' + FROM $wpdb->postmeta + WHERE meta_key='_option_taxonomy_id' + ) mt ON tt.taxonomy = mt.tax_id + WHERE mt.meta_key IN ($string_meta_ids) + AND tr.object_id IN ( + SELECT id + FROM $wpdb->posts + WHERE $wpdb->posts.post_type = '$collection_post_type' + ) + GROUP BY mt.meta_key + ) m + ON (p.id = m.meta_key) + WHERE p.id IN($string_meta_ids) + " + ); + $res = $wpdb->get_results($sql_statement); + //return ['t' => $res, 's' => $sql_statement]; + return $res; + } + + private function query_count_used_taxononomies() { + global $wpdb; + $sql_statement = $wpdb->prepare( + "SELECT COUNT(DISTINCT($wpdb->postmeta.meta_value)) + FROM $wpdb->postmeta + WHERE meta_key = '_option_taxonomy_id' + " + ); + + $res = intval($wpdb->get_var( $sql_statement )); + return $res; + } + + public function get_activities($request) { + $response = array( + 'totals' => [] + ); + $collection_id = false; + if(isset($request['collection_id'])) { + $collection_id = $request['collection_id']; + } + + if( isset($request['start']) ) { + $start = new \DateTime($request['start']); + + $key_cache_object = 'activities_' . $start->format('Y-m-d') . '_' . $collection_id; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + + $end_limit = $start->add(new \DateInterval('P1Y1D'))->setTime(0,0,0); + $end = isset($request['end']) ? new \DateTime($request['end']) : $end_limit; + if($end > $end_limit) + $end = $end_limit; + $interval = [ + 'start' => (new \DateTime($request['start']))->format('Y-m-d H:i:s'), + 'end' => $end->format('Y-m-d H:i:s') + ]; + } else { + $key_cache_object = 'activities_' . $collection_id; + $cached_object = $this->get_cache_object($key_cache_object, $request); + if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200); + $end = (new \DateTime())->add(new \DateInterval('P1D'))->setTime(0,0,0); + $interval = [ + 'end' => $end->format('Y-m-d H:i:s'), + 'start' => $end->sub(new \DateInterval('P1Y1D'))->format('Y-m-d H:i:s') + ]; + } + + $response['totals'] = array( + 'by_interval' => array( + 'start' => $interval['start'], + 'end' => $interval['end'], + 'general' => $this->get_activities_general($collection_id, $interval), + 'by_user' => $this->get_activities_general_by_user($collection_id, $interval) + ), + 'by_user' => $this->get_activities_users($collection_id) + ); + $this->set_cache_object($key_cache_object, $response); + return new \WP_REST_Response($response, 200); + } + + private function get_activities_general($collection_id = false, $interval) { + global $wpdb; + $collection_from = ""; + $start = $interval['start']; + $end = $interval['end']; + if($collection_id !== false) { + $collection_from = "INNER JOIN $wpdb->postmeta pm ON p.id = pm.post_id AND (pm.meta_key='collection_id' AND pm.meta_value='$collection_id')"; + } + $sql_statement = $wpdb->prepare( + "SELECT count(DISTINCT (unix_timestamp(p.post_date) DIV 60)) as total, DATE(p.post_date) as date + FROM $wpdb->posts p $collection_from + WHERE p.post_type='tainacan-log' AND p.post_date BETWEEN '$start' AND '$end' + GROUP BY DATE(p.post_date) + ORDER BY DATE(p.post_date)" + ); + return $wpdb->get_results($sql_statement); + } + + private function get_activities_general_by_user($collection_id = false, $interval) { + global $wpdb; + $collection_from = ""; + $start = $interval['start']; + $end = $interval['end']; + if($collection_id !== false) { + $collection_from = "INNER JOIN $wpdb->postmeta pm ON p.id = pm.post_id AND (pm.meta_key='collection_id' AND pm.meta_value='$collection_id')"; + } + $sql_statement = $wpdb->prepare( + "SELECT p.post_author as user_id, count(DISTINCT (unix_timestamp(p.post_date) DIV 60)) as total, DATE(p.post_date) as date + FROM $wpdb->posts p $collection_from + WHERE p.post_type='tainacan-log' AND p.post_date BETWEEN '$start' AND '$end' + GROUP BY p.post_author, DATE(p.post_date) + ORDER BY DATE(p.post_date)" + ); + $data =$wpdb->get_results($sql_statement); + $arr = array(); + $avatar_sizes = rest_get_avatar_sizes(); + foreach ($data as $item) { + if(!isset($arr[$item->user_id])) { + $user_data = get_userdata($item->user_id); + $urls = array(); + foreach ( $avatar_sizes as $size ) { + $urls[ $size ] = get_avatar_url( $user_data, array( 'size' => $size ) ); + } + $arr[$item->user_id] = [ + 'user' => !$user_data ? [] : [ + 'id' => $user_data->ID, + 'username' => $user_data->user_login, + 'name' => $user_data->display_name, + 'first_name' => $user_data->first_name, + 'last_name' => $user_data->last_name, + 'email' => $user_data->user_email, + 'avatar_urls' => $urls, + ], + 'user_id' => $item->user_id, + 'total' => 0, + 'by_date' => [] + ]; + } + $arr[$item->user_id]['by_date'][] = $item; + $arr[$item->user_id]['total'] += $item->total; + } + return array_values($arr); + } + + private function get_activities_users($collection_id = false) { + global $wpdb; + $collection_from = ""; + if($collection_id !== false) { + $collection_from = "INNER JOIN $wpdb->postmeta pm_col ON p.id = pm_col.post_id AND (pm_col.meta_key='collection_id' AND pm_col.meta_value='$collection_id')"; + } + $sql_statement = $wpdb->prepare( + "SELECT count(DISTINCT (unix_timestamp(p.post_date) DIV 60)) as total, p.post_author as user, pm.meta_value as action + FROM $wpdb->posts p + INNER JOIN $wpdb->postmeta pm ON p.id = pm.post_id AND pm.meta_key = 'action' + $collection_from + WHERE p.post_type='tainacan-log' + GROUP BY p.post_author, pm.meta_value + ORDER BY total DESC" + ); + $results = $wpdb->get_results($sql_statement); + $response = []; + $avatar_sizes = rest_get_avatar_sizes(); + foreach($results as $key => $result) { + $user = $result->user; + $total = $result->total; + $action = $result->action; + if(!isset($response[$user])) { + $user_data = get_userdata($user); + $urls = array(); + foreach ( $avatar_sizes as $size ) { + $urls[ $size ] = get_avatar_url( $user_data, array( 'size' => $size ) ); + } + $response[$user] = [ + 'user' => !$user_data ? [] : [ + 'id' => $user_data->ID, + 'username' => $user_data->user_login, + 'name' => $user_data->display_name, + 'first_name' => $user_data->first_name, + 'last_name' => $user_data->last_name, + 'email' => $user_data->user_email, + 'avatar_urls' => $urls, + ], + 'user_id' => $user, + 'total' => 0, + 'by_action' => [] + ]; + } + $response[$user]['by_action'][$action] = intval($total); + $response[$user]['total'] += $total; + } + return array_values($response); + } + + private $prefix_transient_cahce = 'reports_tnc_'; + + private function get_cache_object($key, $request) { + if ( !isset($request['force']) || $request['force'] == 'no' ) { + $transient = get_transient($this->prefix_transient_cahce . $key); + return $transient; + } + return false; + } + + private function set_cache_object($key, $data) { + $expiration = 604800; //one week + $data['report_cached_on'] = (new \DateTime())->format('Y-m-d H:i:s'); + return set_transient($this->prefix_transient_cahce . $key, $data, $expiration); + } +} + +?> \ No newline at end of file diff --git a/src/classes/api/tainacan-rest-creator.php b/src/classes/api/tainacan-rest-creator.php index a04a0d118..adb894efb 100644 --- a/src/classes/api/tainacan-rest-creator.php +++ b/src/classes/api/tainacan-rest-creator.php @@ -2,27 +2,28 @@ const TAINACAN_REST_NAMESPACE = 'tainacan/v2'; -//$rest_controller = new \Tainacan\API\REST_Controller(); -$rest_collections_controller = new \Tainacan\API\EndPoints\REST_Collections_Controller(); -$rest_items_controller = new \Tainacan\API\EndPoints\REST_Items_Controller(); -$rest_metadata_controller = new \Tainacan\API\EndPoints\REST_Metadata_Controller(); -$rest_taxonomies_controller = new \Tainacan\API\EndPoints\REST_Taxonomies_Controller(); -$rest_terms_controller = new \Tainacan\API\EndPoints\REST_Terms_Controller(); -$rest_filters_controller = new \Tainacan\API\EndPoints\REST_Filters_Controller(); -$rest_item_metadata_controller = new \Tainacan\API\EndPoints\REST_Item_Metadata_Controller(); -$rest_logs_controller = new \Tainacan\API\EndPoints\REST_Logs_Controller(); -$rest_metadata_types_controller = new \Tainacan\API\EndPoints\REST_Metadata_Types_Controller(); -$rest_filter_types_controller = new \Tainacan\API\EndPoints\REST_Filter_Types_Controller(); -$rest_importers_controller = new \Tainacan\API\EndPoints\REST_Importers_Controller(); -$rest_exporters_controller = new \Tainacan\API\EndPoints\REST_Exporters_Controller(); -$rest_background_processes_controller = new \Tainacan\API\EndPoints\REST_Background_Processes_Controller(); -$rest_bulkedit_controller = new \Tainacan\API\EndPoints\REST_Bulkedit_Controller(); -$rest_exposers_controller = new \Tainacan\API\EndPoints\REST_Exposers_Controller(); -$rest_roles_controller = new \Tainacan\API\EndPoints\REST_Roles_Controller(); -new \Tainacan\API\EndPoints\REST_Metadatum_Mappers_Controller(); -$rest_facets_controller = new \Tainacan\API\EndPoints\REST_Facets_Controller(); -$rest_oaipmh_expose_controller = new \Tainacan\API\EndPoints\REST_Oaipmh_Expose_Controller(); -$rest_sequence_edit_controller = new \Tainacan\API\EndPoints\REST_Sequence_Edit_Controller(); +//$rest_controller = new \Tainacan\API\REST_Controller(); +$rest_items_controller = new \Tainacan\API\EndPoints\REST_Items_Controller(); +$rest_terms_controller = new \Tainacan\API\EndPoints\REST_Terms_Controller(); +$rest_logs_controller = new \Tainacan\API\EndPoints\REST_Logs_Controller(); +$rest_roles_controller = new \Tainacan\API\EndPoints\REST_Roles_Controller(); +$rest_facets_controller = new \Tainacan\API\EndPoints\REST_Facets_Controller(); +$rest_reports_controller = new \Tainacan\API\EndPoints\REST_Reports_Controller(); +$rest_filters_controller = new \Tainacan\API\EndPoints\REST_Filters_Controller(); +$rest_exposers_controller = new \Tainacan\API\EndPoints\REST_Exposers_Controller(); +$rest_bulkedit_controller = new \Tainacan\API\EndPoints\REST_Bulkedit_Controller(); +$rest_metadata_controller = new \Tainacan\API\EndPoints\REST_Metadata_Controller(); +$rest_importers_controller = new \Tainacan\API\EndPoints\REST_Importers_Controller(); +$rest_exporters_controller = new \Tainacan\API\EndPoints\REST_Exporters_Controller(); +$rest_taxonomies_controller = new \Tainacan\API\EndPoints\REST_Taxonomies_Controller(); +$rest_collections_controller = new \Tainacan\API\EndPoints\REST_Collections_Controller(); +$rest_filter_types_controller = new \Tainacan\API\EndPoints\REST_Filter_Types_Controller(); +$rest_oaipmh_expose_controller = new \Tainacan\API\EndPoints\REST_Oaipmh_Expose_Controller(); +$rest_item_metadata_controller = new \Tainacan\API\EndPoints\REST_Item_Metadata_Controller(); +$rest_sequence_edit_controller = new \Tainacan\API\EndPoints\REST_Sequence_Edit_Controller(); +$rest_metadata_types_controller = new \Tainacan\API\EndPoints\REST_Metadata_Types_Controller(); +$rest_metadatum_mappers_controller = new \Tainacan\API\EndPoints\REST_Metadatum_Mappers_Controller(); +$rest_background_processes_controller = new \Tainacan\API\EndPoints\REST_Background_Processes_Controller(); // Add here other endpoints imports ?> diff --git a/src/classes/repositories/class-tainacan-taxonomies.php b/src/classes/repositories/class-tainacan-taxonomies.php index e7e330725..c6929bd49 100644 --- a/src/classes/repositories/class-tainacan-taxonomies.php +++ b/src/classes/repositories/class-tainacan-taxonomies.php @@ -208,9 +208,7 @@ class Taxonomies extends Repository { * @return array Entities\Taxonomy * @throws \Exception */ - public function fetch_by_collection( Entities\Collection $collection, $args = [] ) { - $collection_id = $collection->get_id(); - + public function fetch_by_collection( Entities\Collection $collection, $args = [], $output = 'OBJECT' ) { $Tainacan_Metadata = Metadata::get_instance(); // get all taxonomy metadata in this collection @@ -234,7 +232,7 @@ class Taxonomies extends Repository { ]; $args = array_merge($args, $newargs); - return $this->fetch($args, 'OBJECT'); + return $this->fetch($args, $output); } diff --git a/src/classes/tainacan-creator.php b/src/classes/tainacan-creator.php index 188137afd..b3e8023b9 100644 --- a/src/classes/tainacan-creator.php +++ b/src/classes/tainacan-creator.php @@ -92,8 +92,8 @@ function tainacan_autoload($class_name) { $dir = TAINACAN_TAPI_DIR; if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; } else if( isset( $class_path[1] ) && $class_path[1] === 'OAIPMHExpose' ){ - $dir = TAINACAN_OAIPMH_DIR; - if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; + $dir = TAINACAN_OAIPMH_DIR; + if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR; } else if( isset( $class_path[1] ) && substr($class_path[1], 0, 3) === 'Cli' ){ $dir = TAINACAN_CLI_DIR; } else if( isset( $class_path[1] ) && $class_path[1] === 'Metadata_Types' ) { diff --git a/src/views/admin/js/store/modules/report/actions.js b/src/views/admin/js/store/modules/report/actions.js new file mode 100644 index 000000000..55e57118c --- /dev/null +++ b/src/views/admin/js/store/modules/report/actions.js @@ -0,0 +1,186 @@ +import axios from '../../../axios'; + +export const fetchSummary = ({ commit }, { collectionId, force } ) => { + + let endpoint = '/reports'; + + if (collectionId && collectionId != 'default') + endpoint += '/collection/' + collectionId + '/summary'; + else + endpoint += '/repository/summary'; + + if (force && force === true) + endpoint += '?force=yes' + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let summary = res.data; + + commit('setSummary', summary); + commit('setReportLatestCachedOn', { report: 'summary-' + (collectionId ? collectionId : 'default'), reportLatestCachedOn: res.data.report_cached_on }); + resolve(summary); + }) + .catch(error => reject(error)); + }); +}; + +export const fetchMetadata = ({ commit }, { collectionId, force } ) => { + + let endpoint = '/reports'; + + if (collectionId && collectionId != 'default') + endpoint += '/collection/' + collectionId + '/metadata'; + else + endpoint += '/metadata'; + + if (force && force === true) + endpoint += '?force=yes'; + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let metadata = res.data; + + commit('setMetadata', metadata); + commit('setReportLatestCachedOn', { report: 'metadata-' + (collectionId ? collectionId : 'default'), reportLatestCachedOn: res.data.report_cached_on }); + resolve(metadata); + }) + .catch(error => reject(error)); + }); +}; + +export const fetchMetadataList = ({ commit }, { collectionId, onlyTaxonomies } ) => { + + let endpoint = ''; + + if (collectionId && collectionId != 'default') + endpoint += '/collection/' + collectionId + '/metadata/'; + else + endpoint += '/metadata/'; + + if (onlyTaxonomies) + endpoint += '?metaquery[0][key]=metadata_type&metaquery[0][value]=Tainacan\\Metadata_Types\\Taxonomy'; + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let metadataList = res.data; + + commit('setMetadataList', metadataList); + resolve(metadataList); + }) + .catch(error => reject(error)); + }); +}; + +export const fetchCollectionsList = ({ commit }, force) => { + + let endpoint = '/reports/collection'; + + if (force && force === true) + endpoint += '?force=yes'; + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let collectionsList = res.data.list ? res.data.list : {}; + + commit('setCollectionsList', collectionsList); + commit('setReportLatestCachedOn', { report: 'collections', reportLatestCachedOn: res.data.report_cached_on }); + resolve(collectionsList); + }) + .catch(error => reject(error)); + }); +}; + +export const fetchTaxonomiesList = ({ commit }, force) => { + + let endpoint = '/reports/taxonomy'; + + if (force && force === true) + endpoint += '?force=yes'; + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let taxonomiesList = res.data.list ? res.data.list : {}; + + commit('setTaxonomiesList', taxonomiesList); + commit('setReportLatestCachedOn', { report: 'taxonomies', reportLatestCachedOn: res.data.report_cached_on }); + resolve(taxonomiesList); + }) + .catch(error => reject(error)); + }); +}; + +export const fetchTaxonomyTerms = ({ commit }, { taxonomyId, collectionId, parentTerm, isChildChart, force }) => { + + let endpoint = '/reports'; + + if (collectionId && collectionId != 'default') + endpoint += '/collection/' + collectionId + '/metadata/' + taxonomyId + '?'; + else + endpoint += '/taxonomy/' + taxonomyId + '?'; + + if (force) + endpoint += '&force=yes'; + + if (parentTerm) + endpoint += '&parent=' + parentTerm; + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let taxonomyTerms = {}; + if (collectionId && collectionId != 'default') + taxonomyTerms = res.data.list ? res.data.list : []; + else + taxonomyTerms = res.data.terms ? Object.values(res.data.terms) : []; + + if (isChildChart) { + commit('setTaxonomyChildTerms', taxonomyTerms); + commit('setReportLatestCachedOn', { + report: 'taxonomy-terms-' + (collectionId ? collectionId : 'default') + '-' + taxonomyId + (parentTerm ? '-' + parentTerm : '') + '-is-child-chart', + reportLatestCachedOn: res.data.report_cached_on + }); + } else { + commit('setTaxonomyTerms', taxonomyTerms); + commit('setReportLatestCachedOn', { + report: 'taxonomy-terms-' + (collectionId ? collectionId : 'default') + '-' + taxonomyId + (parentTerm ? '-' + parentTerm : ''), + reportLatestCachedOn: res.data.report_cached_on + }); + } + resolve(taxonomyTerms); + }) + .catch(error => reject(error)); + }); +}; + +export const fetchActivities = ({ commit }, { collectionId, startDate, force } ) => { + + let endpoint = '/reports'; + + if (collectionId && collectionId != 'default') + endpoint += '/collection/' + collectionId + '/activities?'; + else + endpoint += '/activities?'; + + if (startDate) + endpoint += '&start=' + startDate; + + if (force) + endpoint += '&force=yes'; + + return new Promise((resolve, reject) => { + axios.tainacan.get(endpoint) + .then(res => { + let activities = res.data; + + commit('setActivities', activities); + commit('setReportLatestCachedOn', { report: 'activities-' + (collectionId ? collectionId : 'default') + (startDate ? '-' + startDate : ''), reportLatestCachedOn: res.data.report_cached_on }); + resolve(activities); + }) + .catch(error => reject(error)); + }); +}; \ No newline at end of file diff --git a/src/views/admin/js/store/modules/report/getters.js b/src/views/admin/js/store/modules/report/getters.js new file mode 100644 index 000000000..103b9a341 --- /dev/null +++ b/src/views/admin/js/store/modules/report/getters.js @@ -0,0 +1,59 @@ +export const getSummary = state => { + return state.summary; +}; + +export const getMetadata = state => { + return state.metadata; +}; + +export const getMetadataList = state => { + return state.metadataList; +}; + +export const getCollectionsList = state => { + return state.collectionsList; +}; + +export const getTaxonomiesList = state => { + return state.taxonomiesList; +}; + +export const getTaxonomyTerms = state => { + return state.taxonomyTerms; +}; + +export const getTaxonomyChildTerms = state => { + return state.taxonomyChildTerms; +}; + +export const getActivities = state => { + return state.activities; +}; + +export const getStackedBarChartOptions = state => { + return state.stackedBarChartOptions; +}; + +export const getHorizontalBarChartOptions = state => { + return state.horizontalBarChartOptions; +}; + +export const getDonutChartOptions = state => { + return state.donutChartOptions; +}; + +export const getHeatMapChartOptions = state => { + return state.heatMapChartOptions; +}; + +export const getAreaChartOptions = state => { + return state.areaChartOptions; +}; + +export const getTreeMapChartOptions = state => { + return state.treeMapChartOptions; +}; + +export const getReportsLatestCachedOn = state => { + return state.reportsLatestCachedOn; +}; \ No newline at end of file diff --git a/src/views/admin/js/store/modules/report/index.js b/src/views/admin/js/store/modules/report/index.js new file mode 100644 index 000000000..500763026 --- /dev/null +++ b/src/views/admin/js/store/modules/report/index.js @@ -0,0 +1,206 @@ +import * as actions from './actions'; +import * as getters from './getters'; +import * as mutations from './mutations'; + +const state = { + reportsLatestCachedOn: {}, + summary: {}, + taxonomiesList: {}, + collectionsList: {}, + taxonomyTerms: [], + taxonomyChildTerms: [], + metadata: {}, + metadataList: {}, + activities: {}, + stackedBarChartOptions: { + chart: { + type: 'bar', + height: 350, + stacked: true, + toolbar: { + show: true + }, + zoom: { + enabled: true, + autoScaleYaxis: true, + } + }, + title: { + text: '' + }, + responsive: [{ + breakpoint: 480, + options: { + legend: { + position: 'bottom', + offsetX: -10, + offsetY: 0 + } + } + }], + plotOptions: { + bar: { + borderRadius: 0, + horizontal: false, + }, + }, + xaxis: { + type: 'category', + tickPlacement: 'on', + categories: [], + tooltip: { enabled: true } + }, + yaxis: { + title: { + text: '' + }, + tooltip: { enabled: true } + }, + legend: { + position: 'right', + offsetY: 40 + }, + fill: { + opacity: 1 + } + }, + donutChartOptions: { + chart: { + type: 'donut', + toolbar: { + show: true + }, + height: 350, + }, + title: { + text: '' + }, + stroke: { + width: 0 + }, + labels: [], + responsive: [{ + breakpoint: 480, + options: { + chart: { + width: 200 + }, + legend: { + position: 'bottom' + } + } + }] + }, + horizontalBarChartOptions: { + chart: { + type: 'bar', + height: 350, + stacked: true, + stackType: '100%', + toolbar: { + show: true + }, + zoom: { + type: 'y', + enabled: true, + autoScaleYaxis: true, + } + }, + plotOptions: { + bar: { + horizontal: true, + }, + }, + title: { + text: '' + }, + xaxis: { + type: 'category', + tickPlacement: 'on', + categories: [], + tooltip: { enabled: true } + }, + yaxis: { + tickPlacement: 'on', + tooltip: { enabled: true } + }, + tooltip: { + enabled: true + }, + fill: { + opacity: 1 + + }, + legend: { + position: 'top', + horizontalAlign: 'left', + offsetX: 40, + } + }, + areaChartOptions: { + chart: { + height: 200, + type: 'area', + toolbar: { + show: true, + tools: { + download: true, + selection: false, + zoom: true, + zoomin: true, + zoomout: true, + pan: true, + } + }, + }, + dataLabels: { + enabled: false + }, + stroke: { + width: 1, + curve: 'smooth' + }, + fill: { + opacity: 1, + type: 'gradient', + gradient: { + shade: 'light', + type: "vertical", + opacityFrom: 0.65, + opacityTo: 0.35, + } + }, + legend: { + position: 'top', + horizontalAlign: 'left' + }, + xaxis: { + type: 'datetime' + }, + yaxis: { + labels: { + minWidth: 64 + } + } + }, + treeMapOptions: { + legend: { + show: false + }, + chart: { + height: 350, + type: 'treemap' + }, + title: { + text: '' + } + } +}; + +export default { + namespaced: true, + state, + mutations, + actions, + getters +} \ No newline at end of file diff --git a/src/views/admin/js/store/modules/report/mutations.js b/src/views/admin/js/store/modules/report/mutations.js new file mode 100644 index 000000000..563a71398 --- /dev/null +++ b/src/views/admin/js/store/modules/report/mutations.js @@ -0,0 +1,61 @@ +import Vue from 'vue'; + +export const setSummary = (state, summary) => { + state.summary = summary; +}; + +export const setMetadata = (state, metadata) => { + state.metadata = metadata; +}; + +export const setMetadataList = (state, metadataList) => { + state.metadataList = metadataList; +}; + +export const setCollectionsList = (state, collectionsList) => { + state.collectionsList = collectionsList; +}; + +export const setTaxonomiesList = (state, taxonomiesList) => { + state.taxonomiesList = taxonomiesList; +}; + +export const setTaxonomyTerms = (state, taxonomyTerms) => { + state.taxonomyTerms = taxonomyTerms; +}; + +export const setTaxonomyChildTerms = (state, taxonomyTerms) => { + state.taxonomyChildTerms = taxonomyTerms; +}; + +export const setActivities = (state, activities) => { + state.activities = activities; +}; + +export const setStackedBarChartOptions = (state, stackedBarChartOptions) => { + state.stackedBarChartOptions = stackedBarChartOptions; +}; + +export const setHorizontalBarChartOptions = (state, horizontalBarChartOptions) => { + state.horizontalBarChartOptions = horizontalBarChartOptions; +}; + +export const setDonutChartOptions = (state, donutChartOptions) => { + state.donutChartOptions = donutChartOptions; +}; + +export const setHeatMapChartOptions = (state, heatMapChartOptions) => { + state.heatMapChartOptions = heatMapChartOptions; +}; + +export const setAreaChartOptions = (state, areaChartOptions) => { + state.areaChartOptions = areaChartOptions; +}; + +export const setTreeMapChartOptions = (state, areaChartOptions) => { + state.threeMapChartOptions = areaChartOptions; +}; + +export const setReportLatestCachedOn = (state, { report, reportLatestCachedOn }) => { + Vue.set(state.reportsLatestCachedOn, report, reportLatestCachedOn); +}; \ No newline at end of file diff --git a/src/views/admin/js/store/store.js b/src/views/admin/js/store/store.js index 592cca62f..95dc1efb2 100644 --- a/src/views/admin/js/store/store.js +++ b/src/views/admin/js/store/store.js @@ -14,6 +14,7 @@ import bulkedition from './modules/bulk-edition'; import exporter from './modules/exporter'; import exposer from './modules/exposer'; import capability from './modules/capability'; +import report from './modules/report'; // Vue Dev Tools! Vue.config.devtools = process && process.env && process.env.NODE_ENV === 'development'; @@ -41,6 +42,7 @@ export default new Vuex.Store({ bulkedition, exporter, exposer, - capability + capability, + report } }) \ No newline at end of file diff --git a/src/views/class-tainacan-admin.php b/src/views/class-tainacan-admin.php index bd95c6343..395ea82d5 100644 --- a/src/views/class-tainacan-admin.php +++ b/src/views/class-tainacan-admin.php @@ -58,6 +58,15 @@ class Admin { array( &$this, 'roles_page' ) ); + $reports_page_suffix = add_submenu_page( + $this->menu_slug, + __('Reports', 'tainacan'), + __('Reports', 'tainacan'), + 'read', + 'tainacan_reports', + array( &$this, 'reports_page' ) + ); + add_submenu_page( $this->menu_slug, __('Item Submission', 'tainacan'), @@ -69,6 +78,7 @@ class Admin { add_action( 'load-' . $page_suffix, array( &$this, 'load_admin_page' ) ); add_action( 'load-' . $roles_page_suffix, array( &$this, 'load_roles_page' ) ); + add_action( 'load-' . $reports_page_suffix, array( &$this, 'load_reports_page' ) ); } function load_admin_page() { @@ -82,6 +92,11 @@ class Admin { add_action( 'admin_enqueue_scripts', array( &$this, 'add_roles_js' ), 90 ); } + function load_reports_page() { + add_action( 'admin_enqueue_scripts', array( &$this, 'add_reports_css' ), 90 ); + add_action( 'admin_enqueue_scripts', array( &$this, 'add_reports_js' ), 90 ); + } + function login_styles_reset( $style ) { if ( strpos( $style, 'wp-admin-css' ) !== false ) { $style = null; @@ -130,6 +145,34 @@ class Admin { echo "
"; } + function add_reports_css() { + global $TAINACAN_BASE_URL; + + wp_enqueue_style( 'tainacan-fonts', $TAINACAN_BASE_URL . '/assets/css/tainacanicons.css', [], TAINACAN_VERSION ); + wp_enqueue_style( 'tainacan-reports-page', $TAINACAN_BASE_URL . '/assets/css/tainacan-reports.css', [], TAINACAN_VERSION ); + } + + function add_reports_js() { + + global $TAINACAN_BASE_URL; + + wp_enqueue_script( 'tainacan-reports', $TAINACAN_BASE_URL . '/assets/js/reports.js', ['underscore', 'wp-i18n'], TAINACAN_VERSION, true ); + wp_set_script_translations('tainacan-reports', 'tainacan'); + + $settings = $this->get_admin_js_localization_params(); + wp_localize_script( 'tainacan-reports', 'tainacan_plugin', $settings ); + wp_enqueue_script('underscore'); + wp_enqueue_script('wp-i18n'); + + do_action('tainacan-enqueue-reports-scripts'); + } + + function reports_page() { + global $TAINACAN_BASE_URL; + // TODO move it to a separate file and start the Vue project + echo "
"; + } + function add_admin_css() { global $TAINACAN_BASE_URL; diff --git a/src/views/reports/components/activities-block.vue b/src/views/reports/components/activities-block.vue new file mode 100644 index 000000000..53a6b49f4 --- /dev/null +++ b/src/views/reports/components/activities-block.vue @@ -0,0 +1,348 @@ + + + + + \ No newline at end of file diff --git a/src/views/reports/components/activities-per-user-block.vue b/src/views/reports/components/activities-per-user-block.vue new file mode 100644 index 000000000..2e7e9c4eb --- /dev/null +++ b/src/views/reports/components/activities-per-user-block.vue @@ -0,0 +1,153 @@ + + + + + \ No newline at end of file diff --git a/src/views/reports/components/collections-list-block.vue b/src/views/reports/components/collections-list-block.vue new file mode 100644 index 000000000..210325415 --- /dev/null +++ b/src/views/reports/components/collections-list-block.vue @@ -0,0 +1,105 @@ + + + \ No newline at end of file diff --git a/src/views/reports/components/items-per-term-block.vue b/src/views/reports/components/items-per-term-block.vue new file mode 100644 index 000000000..af4ff2858 --- /dev/null +++ b/src/views/reports/components/items-per-term-block.vue @@ -0,0 +1,243 @@ + + + \ No newline at end of file diff --git a/src/views/reports/components/items-per-term-collection-block.vue b/src/views/reports/components/items-per-term-collection-block.vue new file mode 100644 index 000000000..b969c56b8 --- /dev/null +++ b/src/views/reports/components/items-per-term-collection-block.vue @@ -0,0 +1,776 @@ + + + + + \ No newline at end of file diff --git a/src/views/reports/components/metadata-distribution-block.vue b/src/views/reports/components/metadata-distribution-block.vue new file mode 100644 index 000000000..934f1d593 --- /dev/null +++ b/src/views/reports/components/metadata-distribution-block.vue @@ -0,0 +1,147 @@ + + + + + \ No newline at end of file diff --git a/src/views/reports/components/metadata-types-block.vue b/src/views/reports/components/metadata-types-block.vue new file mode 100644 index 000000000..0ca8c645b --- /dev/null +++ b/src/views/reports/components/metadata-types-block.vue @@ -0,0 +1,132 @@ + + + \ No newline at end of file diff --git a/src/views/reports/components/number-block.vue b/src/views/reports/components/number-block.vue new file mode 100644 index 000000000..4a12dbcac --- /dev/null +++ b/src/views/reports/components/number-block.vue @@ -0,0 +1,103 @@ + + + + + \ No newline at end of file diff --git a/src/views/reports/components/terms-per-taxonomy-block.vue b/src/views/reports/components/terms-per-taxonomy-block.vue new file mode 100644 index 000000000..8af18a241 --- /dev/null +++ b/src/views/reports/components/terms-per-taxonomy-block.vue @@ -0,0 +1,119 @@ + + + + \ No newline at end of file diff --git a/src/views/reports/js/reports-main.js b/src/views/reports/js/reports-main.js new file mode 100644 index 000000000..cbe7b4ee1 --- /dev/null +++ b/src/views/reports/js/reports-main.js @@ -0,0 +1,104 @@ +import Vue from 'vue'; +import store from '../../admin/js/store/store'; +import router from './reports-router'; +import VTooltip from 'v-tooltip'; +import { Snackbar, Modal } from 'buefy'; +import VueApexCharts from 'vue-apexcharts'; +import cssVars from 'css-vars-ponyfill'; +import { + I18NPlugin, + UserCapabilitiesPlugin, + StatusHelperPlugin, +} from '../../admin/js/admin-utilities'; + +// Vue Dev Tools! +Vue.config.devtools = process && process.env && process.env.NODE_ENV === 'development'; + +import ReportsPage from '../reports.vue'; +import NumberBlock from '../components/number-block.vue'; +import ItemsPerTermBlock from '../components/items-per-term-block.vue'; +import ItemsPerTermCollectionBlock from '../components/items-per-term-collection-block.vue'; +import TermsPerTaxonomyBlock from '../components/terms-per-taxonomy-block.vue'; +import MetadataTypesBlock from '../components/metadata-types-block.vue'; +import MetadataDistributionBlock from '../components/metadata-distribution-block.vue'; +import CollectionsListBlock from '../components/collections-list-block.vue'; +import ActivitiesBlock from '../components/activities-block.vue'; +import ActivitiesPerUserBlock from '../components/activities-per-user-block.vue'; + +Vue.use(VueApexCharts) + +Apex.colors = [ + '#298596', // Tainacan Turquoise + '#01295c', // Tainacan Blue + '#25a189', // Tainacan Green + '#e69810', // Tainacan Yellow + '#a23939', // Tainacan Red + '#592570', // Tainacan Purple + '#ed4f63', // Tainacan Pink + '#b46659', // Tainacan Brown + '#e5721c', // Tainacan Orange + '#04a5ff', // Tainacan Other Blue + '#454647' // Tainacan Dark Gray +]; + +Vue.use(I18NPlugin); +Vue.use(UserCapabilitiesPlugin); +Vue.use(StatusHelperPlugin); +Vue.use(VTooltip); +Vue.use(Snackbar); +Vue.use(Modal); + +Vue.component('number-block', NumberBlock); +Vue.component('items-per-term-block', ItemsPerTermBlock); +Vue.component('items-per-term-collection-block', ItemsPerTermCollectionBlock); +Vue.component('terms-per-taxonomy-block', TermsPerTaxonomyBlock); +Vue.component('metadata-types-block', MetadataTypesBlock); +Vue.component('metadata-distribution-block', MetadataDistributionBlock); +Vue.component('collections-list-block', CollectionsListBlock); +Vue.component('activities-block', ActivitiesBlock); +Vue.component('activities-per-user-block', ActivitiesPerUserBlock); +Vue.component('apexchart', VueApexCharts); + +// Changing title of pages +router.beforeEach((to, from, next) => { + document.title = to.meta.title; + if (next() != undefined) + next(); +}); + +new Vue({ + el: '#tainacan-reports-app', + store, + router, + render: h => h(ReportsPage) +}); + +listen("load", window, function() { + var iconsStyle = document.createElement("style"); + iconsStyle.setAttribute('type', 'text/css'); + iconsStyle.innerText = '.tainacan-icon::before{ opacity: 1.0 !important; }'; + document.head.appendChild(iconsStyle); +}); + +// Initialize Ponyfill for Custom CSS properties +cssVars({ + // Options... +}); + +// Display Icons only once everything is loaded +function listen(evnt, elem, func) { + if (elem.addEventListener) // W3C DOM + elem.addEventListener(evnt,func,false); + else if (elem.attachEvent) { // IE DOM + var r = elem.attachEvent("on"+evnt, func); + return r; + } else if (document.head) { + var iconHideStyle = document.createElement("style"); + iconHideStyle.innerText = '.tainacan-icon::before{ opacity: 0.0 !important; }'; + document.head.appendChild(iconHideStyle); + } else { + var iconHideStyle = document.createElement("style"); + iconHideStyle.innerText = '.tainacan-icon::before{ opacity: 0.0 !important; }'; + document.getElementsByTagName("head")[0].appendChild(iconHideStyle); + } +} \ No newline at end of file diff --git a/src/views/reports/js/reports-mixin.js b/src/views/reports/js/reports-mixin.js new file mode 100644 index 000000000..59747dfef --- /dev/null +++ b/src/views/reports/js/reports-mixin.js @@ -0,0 +1,13 @@ +export const reportsChartMixin = { + props: { + chartData: {}, + isFetchingData: false + }, + data () { + return { + isBuildingChart: false, + chartSeries: [], + chartOptions: {} + } + } +}; diff --git a/src/views/reports/js/reports-router.js b/src/views/reports/js/reports-router.js new file mode 100644 index 000000000..5de88c64d --- /dev/null +++ b/src/views/reports/js/reports-router.js @@ -0,0 +1,29 @@ +import Vue from 'vue'; +import VueRouter from 'vue-router' +import qs from 'qs'; + +import ReportsList from '../pages/reports-list.vue'; + +const { __ } = wp.i18n; + +Vue.use(VueRouter); + +const routes = [ + { path: '/', redirect:'/reports' }, + { path: '/reports', name: 'ReportsList', component: ReportsList, meta: { title: __('Tainacan Reports') } }, + + { path: '*', redirect: '/'} +]; + +export default new VueRouter ({ + routes, + // set custom query resolver + parseQuery(query) { + return qs.parse(query); + }, + stringifyQuery(query) { + let result = qs.stringify(query); + + return result ? ('?' + result) : ''; + } +}); \ No newline at end of file diff --git a/src/views/reports/pages/reports-list.vue b/src/views/reports/pages/reports-list.vue new file mode 100644 index 000000000..453c91710 --- /dev/null +++ b/src/views/reports/pages/reports-list.vue @@ -0,0 +1,389 @@ + + + \ No newline at end of file diff --git a/src/views/reports/reports.vue b/src/views/reports/reports.vue new file mode 100644 index 000000000..cea64b40b --- /dev/null +++ b/src/views/reports/reports.vue @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/src/views/reports/scss/reports-basics.sass b/src/views/reports/scss/reports-basics.sass new file mode 100644 index 000000000..3d3407fe3 --- /dev/null +++ b/src/views/reports/scss/reports-basics.sass @@ -0,0 +1,14 @@ +@import "../../../../node_modules/bulma/sass/utilities/_all.sass" +@import "../../../../node_modules/bulma/sass/helpers/_all.sass" +@import "../../../../node_modules/bulma/sass/form/_all.sass" +@import "../../../../node_modules/bulma/sass/grid/_all.sass" +@import "../../../../node_modules/bulma/sass/components/pagination.sass" +@import "../../../../node_modules/bulma/sass/components/card.sass" +@import "../../../../node_modules/bulma/sass/elements/icon.sass" +@import "../../../../node_modules/bulma/sass/elements/tag.sass" +@import "../../../../node_modules/bulma/sass/elements/title.sass" +@import "../../../../node_modules/bulma/sass/elements/notification.sass" +@import "../../../../node_modules/bulma/sass/components/tabs.sass" +@import "../../../../node_modules/bulma/sass/elements/button.sass" +@import "../../../../node_modules/bulma/sass/components/dropdown.sass" +@import "../../../../node_modules/bulma/sass/components/modal.sass" \ No newline at end of file diff --git a/src/views/reports/tainacan-reports.scss b/src/views/reports/tainacan-reports.scss new file mode 100644 index 000000000..d0fbbaefb --- /dev/null +++ b/src/views/reports/tainacan-reports.scss @@ -0,0 +1,135 @@ +#tainacan-reports-app { + padding: 10px 2px 10px 2px; + margin: 0; + + // Tainacan Loading + .tainacan-icon::before { + opacity: 0.0; // Will make it 1 once window.load is done; + } + + a:hover { + cursor: pointer; + } + + .wp-heading-inline { + margin-bottom: 2rem; + } + + .columns { + max-width: 100%; + align-items: flex-start; + justify-content: center; + + .column { + max-width: 100%; + padding: 0; + position: relative; + box-sizing: content-box; + + .postbox { + margin: 0.75rem; + } + .box-last-cached-on { + position: absolute; + bottom: calc(1px + 0.75rem); + right: calc(1px + 0.75rem); + display: inline-block; + padding: 0px 10px; + background-color: var(--tainacan-block-gray2, #dbdbdb); + color: var(--tainacan-block-gray4, #555758); + font-size: 0.875em; + border-top-left-radius: 3px; + opacity: 0.0; + transition: opacity 0.3s ease; + + button { + border: none; + background: none; + cursor: pointer; + + &:hover { + color: var(--wp-admin-theme-color, #007cba); + } + } + } + .postbox:hover+.box-last-cached-on, + .postbox:hover+.box-last-cached-on+.box-last-cached-on, + .box-last-cached-on:hover { + opacity: 1.0; + } + } + } + + .postbox { + padding: 1.125rem 1.25rem; + margin-bottom: 0; + height: 100%; + min-height: 380px; + background-color: var(--tainacan-block-gray0, #f6f6f6); + + label { + font-weight: bold; + font-size: 0.875rem; + } + + .box-header { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; + + .box-header__item { + margin-bottom: 10px; + line-height: 2rem; + } + } + } + .graph-mode-switch { + display: inline-block; + button { + border: none !important; + background: none !important; + box-shadow: none !important; + padding: 0; + cursor: pointer; + + &.current { + color: var(--wp-admin-theme-color, #007cba); + } + } + } + + .tainacan-custom-tooltip { + padding: 0; + display: flex; + justify-content: center; + align-items: flex-start; + flex-direction: column; + + .tainacan-custom-tooltip__header { + background-color: var(--tainacan-block-gray1, #f2f2f2); + display: flex; + justify-content: flex-start; + align-items: center; + width: 100%; + padding: 6px 10px 4px 10px; + } + + .tainacan-custom-tooltip__header+.tainacan-custom-tooltip__body { + padding: 4px 10px 6px 10px; + } + .tainacan-custom-tooltip__body { + width: 100%; + padding: 6px 10px; + display: flex; + justify-content: center; + align-items: flex-start; + flex-direction: column; + + p { + margin-bottom: 4px; + font-size: 0.85rem; + } + } + } +} \ No newline at end of file diff --git a/src/views/tainacan-admin-i18n.php b/src/views/tainacan-admin-i18n.php index d5f3ef749..5ae6529f8 100644 --- a/src/views/tainacan-admin-i18n.php +++ b/src/views/tainacan-admin-i18n.php @@ -22,6 +22,7 @@ return apply_filters( 'tainacan-admin-i18n', [ 'items' => __( 'Items', 'tainacan' ), /* translators: Plural, a list of metadata */ 'metadata' => __( 'Metadata', 'tainacan' ), + 'metadata_types' => __( 'Metadata types', 'tainacan' ), 'filters' => __( 'Filters', 'tainacan' ), 'taxonomies' => __( 'Taxonomies', 'tainacan' ), 'activities' => __( 'Activities', 'tainacan' ), @@ -194,6 +195,7 @@ return apply_filters( 'tainacan-admin-i18n', [ 'label_button_delete_thumb' => __( 'Button Delete Thumbnail', 'tainacan' ), 'label_collections_per_page' => __( 'Collections per Page:', 'tainacan' ), 'label_taxonomies_per_page' => __( 'Taxonomies per Page:', 'tainacan' ), + 'label_terms_per_page' => __( 'Terms per Page:', 'tainacan' ), 'label_activities_per_page' => __( 'Activities per Page:', 'tainacan' ), 'label_items_per_page' => __( 'Items per Page:', 'tainacan' ), 'label_attachments_per_page' => __( 'Attachments per Page:', 'tainacan' ), @@ -536,10 +538,42 @@ return apply_filters( 'tainacan-admin-i18n', [ 'label_switch_document_type' => __( 'Switch document type', 'tainacan' ), 'label_sending_form' => __( 'Sending form...', 'tainacan' ), 'label_form_not_loaded' => __( 'This form could not be loaded', 'tainacan' ), + 'label_terms_not_used' => __( 'Terms not used', 'tainacan' ), + 'label_terms_used' => __( 'Terms used', 'tainacan' ), + 'label_number_of_terms' => __( 'Number of terms', 'tainacan' ), + 'label_number_of_items' => __( 'Number of items', 'tainacan' ), + 'label_usage_of_terms_per_taxonomy' => __( 'Usage of terms per taxonomy', 'tainacan' ), + 'label_items_per_term' => __( 'Items per term', 'tainacan' ), + 'label_items_per_term_from_taxonomy' => __( 'Items per term from taxonomy:', 'tainacan' ), + 'label_items_per_term_from_taxonomy_metadatum' => __( 'Items per term from taxonomy metadatum:', 'tainacan' ), + 'label_items_per_child_terms_of' => __( 'Items per child terms of:', 'tainacan' ), + 'label_items_per_collection' => __( 'Items per collection', 'tainacan' ), + 'label_loading_report' => __( 'Loading report...', 'tainacan' ), + 'label_metadata_fill_distribution' => __( 'Metadata fill distribution', 'tainacan' ), + 'label_not_filled' => __( 'Not filled yet', 'tainacan' ), + 'label_filled' => __( 'Already filled', 'tainacan' ), + /* translators: To be displayed with the number of Taxonomies not used in the colllection */ + 'label_not_used' => __( 'Not used', 'tainacan' ), + /* translators: To be displayed with the number of Taxonomies used in the colllection */ + 'label_select_a_taxonomy' => __( 'Select a taxonomy', 'tainacan' ), + 'label_used' => __( 'Used', 'tainacan' ), + 'label_select_a_taxonomy_metadatum' => __( 'Select a taxonomy metadatum', 'tainacan' ), + 'label_items_with_this_metadum_value' => __( 'Items with this metadatum value', 'tainacan' ), + 'label_amount_of_items_per_metadatum_value' => __( 'Amount of items per metadatum value', 'tainacan' ), + 'label_activities_during_year' => __( 'Activities during the year', 'tainacan' ), 'label_compact_list' => __( 'Compact list', 'tainacan'), 'label_detailed_list' => __( 'Detailed list', 'tainacan'), 'label_view_metadata_details' => __( 'View metadata details', 'tainacan'), 'label_filter_by_metadata_type' => __( 'Filter by metadatum type', 'tainacan'), + 'label_pie_chart' => __( 'Pie chart', 'tainacan'), + 'label_bar_chart' => __( 'Bar chart', 'tainacan'), + 'label_terms_per_page' => __( 'Terms per page', 'tainacan'), + 'label_anonymous_user' => __( 'Anonymous User', 'tainacan'), + 'label_select_a_year' => __( 'Select a year', 'tainacan'), + 'label_all_users' => __( 'All users', 'tainacan'), + 'label_activitiy_per_user' => __( 'Activities per user', 'tainacan'), + 'label_report_generated_on' => __( 'Report generated on', 'tainacan'), + 'label_get_latest_report' => __( 'Get latest report', 'tainacan'), 'label_decrease' => __( 'Decrease', 'tainacan'), 'label_increase' => __( 'Increase', 'tainacan'), @@ -602,7 +636,9 @@ return apply_filters( 'tainacan-admin-i18n', [ 'instruction_click_error_to_go_to_metadata' => __( 'Click on the error to go to the metadata:', 'tainacan'), 'instruction_click_to_see_or_search' => __( 'Click to see options or type to search...', 'tainacan'), 'instruction_select_one_or_more_collections' => __( 'Select one or more collections', 'tainacan'), - 'instruction_thumbnail_alt' => __( 'Type here a descriptive text for the image thumbnail...', 'tainacan'), + 'instruction_thumbnail_alt' => __( 'Type here a descriptive text for the image thumbnail...', 'tainacan'), + 'instruction_click_to_see_%s_child_terms' => __( 'Click to see %s child terms', 'tainacan'), + 'instruction_click_to_see_%s_child_term' => __( 'Click to see %s child term', 'tainacan'), // Info. Other feedback to user. 'info_items_tab_all' => __( 'Every published item, including those visible only to editors.', 'tainacan' ), @@ -708,7 +744,7 @@ return apply_filters( 'tainacan-admin-i18n', [ 'info_warning_taxonomy_not_saved' => __( 'Are you sure? The taxonomy is not saved, changes will be lost.', 'tainacan' ), 'info_warning_terms_not_saved' => __( 'Are you sure? There are terms not saved, changes will be lost.', 'tainacan' ), 'info_warning_orphan_terms' => __( 'Are you sure? This term is parent of other terms. These will be converted to root terms.', 'tainacan' ), - 'info_no_activities' => __( 'No activities yet.', 'tainacan' ), + 'info_no_activities' => __( 'No activities found.', 'tainacan' ), 'info_logs_before' => __( 'Before', 'tainacan' ), 'info_logs_after' => __( 'After', 'tainacan' ), 'info_there_is_no_metadatum' => __( 'There is no metadata here yet.', 'tainacan' ), @@ -821,6 +857,23 @@ return apply_filters( 'tainacan-admin-i18n', [ 'info_recaptcha_link_%s' => __( 'Remember to configure your website reCAPTCHA keys on the item submission repository page.', 'tainacan'), 'info_form_not_loaded' => __( 'There are probably not enought permissions to display it here.', 'tainacan'), 'info_validating_slug' => __( 'Validating slug...', 'tainacan'), + + 'label_amount_of_metadata_of_type' => __( 'Amount of metadata of this type', 'tainacan'), + 'info_child_terms_chart' => __( 'Click on the term bar on the chart aside to see its child terms (if any) in this panel', 'tainacan' ), + + /* Activity actions */ + 'action_update-metadata-value' => __( 'Item Metadata Value Updates', 'tainacan'), + 'action_update' => __( 'General Updates', 'tainacan'), + 'action_create' => __( 'General Creations', 'tainacan'), + 'action_update-metadata-order' => __( 'Metadata Order Updates', 'tainacan'), + 'action_trash' => __( 'Trashing', 'tainacan'), + 'action_new-attachment' => __( 'Addition of Attachments', 'tainacan'), + 'action_update-filters-order' => __( 'Filter Order Updates', 'tainacan'), + 'action_update-document' => __( 'Document Updates', 'tainacan'), + 'action_delete' => __( 'General Deletions', 'tainacan'), + 'action_delete-attachment' => __( 'Deletion of Attachments', 'tainacan'), + 'action_update-thumbnail' => __( 'Thumbnail Updates', 'tainacan'), + 'action_others' => __( 'Other Actions', 'tainacan'), 'info_applied_filters' => __( 'filters applied', 'tainacan'), 'info_items_found' => __( 'items found', 'tainacan'), 'info_applied_filter' => __( 'filter applied', 'tainacan'), diff --git a/webpack.common.js b/webpack.common.js index 94122d9a6..545c16876 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -8,6 +8,7 @@ module.exports = { theme_search: './src/views/theme-search/js/theme-main.js', item_submission: './src/views/item-submission/js/item-submission-main.js', roles: './src/views/roles/js/roles-main.js', + reports: './src/views/reports/js/reports-main.js', block_terms_list: './src/views/gutenberg-blocks/tainacan-terms/terms-list/index.js',