From b9230dabae41ce58db4b91d33bb3af077fd3152a Mon Sep 17 00:00:00 2001 From: Ricardo Aragon Date: Thu, 11 Apr 2024 12:59:26 -0300 Subject: [PATCH] fix: a11y improvements (#155) * fix: move totals to a separate table * fix: don't consider current institution on duplicate name check * fix: tests for duplicate institution name * fix: book limit input accessibility * feat: add feature tests for InstitutionsTable * fix: attempt to inject content immediately after page load * fix: update message in the assign books page * fix: clean up db after each test * fix: merge conflict * fix: improve institutionsTotals class * fix: inject alert content after page load * fix: full size table for smaller screens * chore: bump dependencies * fix: add `error-list` class back * fix: remove `is-dismissible` class * fix: remove `role` attribute since WP adds it dynamically * fix: add `type="text"` * fix: replace title with screen reader information --------- Co-authored-by: Felipe Dalcin Co-authored-by: Oscar Arzola --- composer.lock | 75 ++--- .../{app-f5d07064.js => app-81345697.js} | 2 +- ...pp-f5d07064.js.map => app-81345697.js.map} | 2 +- .../pressbooks-multi-institution-34e9eba8.css | 1 + .../pressbooks-multi-institution-6c8c8aa3.css | 1 - dist/manifest.json | 6 +- package-lock.json | 259 ++---------------- .../styles/pressbooks-multi-institutions.css | 10 + resources/views/assign/index.blade.php | 14 +- resources/views/institutions/form.blade.php | 53 ++-- resources/views/institutions/index.blade.php | 16 +- .../views/institutions/rows/totals.blade.php | 5 - resources/views/institutions/totals.blade.php | 20 ++ resources/views/table/book-admins.blade.php | 2 +- src/Controllers/InstitutionsController.php | 16 +- src/Views/InstitutionsTable.php | 68 ----- src/Views/InstitutionsTotals.php | 98 +++++++ .../InstitutionsControllerTest.php | 40 ++- .../Feature/Views/InstitutionsTotalsTest.php | 103 +++++++ tests/TestCase.php | 7 + tests/Traits/CreatesModels.php | 8 - 21 files changed, 407 insertions(+), 399 deletions(-) rename dist/assets/{app-f5d07064.js => app-81345697.js} (92%) rename dist/assets/{app-f5d07064.js.map => app-81345697.js.map} (94%) create mode 100644 dist/assets/pressbooks-multi-institution-34e9eba8.css delete mode 100644 dist/assets/pressbooks-multi-institution-6c8c8aa3.css delete mode 100644 resources/views/institutions/rows/totals.blade.php create mode 100644 resources/views/institutions/totals.blade.php create mode 100644 src/Views/InstitutionsTotals.php create mode 100644 tests/Feature/Views/InstitutionsTotalsTest.php diff --git a/composer.lock b/composer.lock index 63da81377..7a8f20e2b 100644 --- a/composer.lock +++ b/composer.lock @@ -1050,16 +1050,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.4.1", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "06450585bf65e978026bda220cdebca3f867fde7" + "reference": "43810bdb2ddb5400e5c5e778e27b210a0ca83b6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", - "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/43810bdb2ddb5400e5c5e778e27b210a0ca83b6b", + "reference": "43810bdb2ddb5400e5c5e778e27b210a0ca83b6b", "shasum": "" }, "require": { @@ -1108,7 +1108,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.2" }, "funding": [ { @@ -1124,7 +1124,7 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "voku/portable-ascii", @@ -1274,16 +1274,16 @@ }, { "name": "laravel/pint", - "version": "v1.14.0", + "version": "v1.15.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e" + "reference": "5f288b5e79938cc72f5c298d384e639de87507c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/6b127276e3f263f7bb17d5077e9e0269e61b2a0e", - "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e", + "url": "https://api.github.com/repos/laravel/pint/zipball/5f288b5e79938cc72f5c298d384e639de87507c6", + "reference": "5f288b5e79938cc72f5c298d384e639de87507c6", "shasum": "" }, "require": { @@ -1294,13 +1294,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.49.0", - "illuminate/view": "^10.43.0", - "larastan/larastan": "^2.8.1", + "friendsofphp/php-cs-fixer": "^3.52.1", + "illuminate/view": "^10.48.4", + "larastan/larastan": "^2.9.2", "laravel-zero/framework": "^10.3.0", - "mockery/mockery": "^1.6.7", + "mockery/mockery": "^1.6.11", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.33.6" + "pestphp/pest": "^2.34.5" }, "bin": [ "builds/pint" @@ -1336,7 +1336,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-02-20T17:38:05+00:00" + "time": "2024-04-02T14:28:47+00:00" }, { "name": "myclabs/deep-copy", @@ -1894,16 +1894,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -1977,7 +1977,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -1993,7 +1993,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "sebastian/cli-parser", @@ -2797,16 +2797,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -2818,7 +2818,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2839,8 +2839,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -2848,8 +2847,7 @@ "type": "github" } ], - "abandoned": true, - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -3012,16 +3010,16 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212" + "reference": "a0f7d708794a738f328d7b6c94380fd1d6c40446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/224e4a1329c03d8bad520e3fc4ec980034a4b212", - "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/a0f7d708794a738f328d7b6c94380fd1d6c40446", + "reference": "a0f7d708794a738f328d7b6c94380fd1d6c40446", "shasum": "" }, "require": { @@ -3029,7 +3027,9 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "yoast/yoastcs": "^2.3.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" }, "type": "library", "extra": { @@ -3066,9 +3066,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2023-08-19T14:25:08+00:00" + "time": "2024-04-05T16:01:51+00:00" } ], "aliases": [], diff --git a/dist/assets/app-f5d07064.js b/dist/assets/app-81345697.js similarity index 92% rename from dist/assets/app-f5d07064.js rename to dist/assets/app-81345697.js index a4f40d5b5..f41fd2513 100644 --- a/dist/assets/app-f5d07064.js +++ b/dist/assets/app-81345697.js @@ -1,2 +1,2 @@ document.addEventListener("DOMContentLoaded",function(){const e=document.querySelector(`${context.formSelector} #doaction`);e&&e.addEventListener("click",function(t){t.preventDefault();const o=document.querySelector(`${context.formSelector} #bulk-action-selector-top`).value,c=document.querySelectorAll(`${context.formSelector} .check-column input:checked`);o!=="-1"&&c.length>0&&confirm(context.confirmationMessage)&&document.querySelector(context.formSelector).submit()})}); -//# sourceMappingURL=app-f5d07064.js.map +//# sourceMappingURL=app-81345697.js.map diff --git a/dist/assets/app-f5d07064.js.map b/dist/assets/app-81345697.js.map similarity index 94% rename from dist/assets/app-f5d07064.js.map rename to dist/assets/app-81345697.js.map index 1d703c698..2bda3e773 100644 --- a/dist/assets/app-f5d07064.js.map +++ b/dist/assets/app-81345697.js.map @@ -1 +1 @@ -{"version":3,"file":"app-f5d07064.js","sources":["../../resources/assets/js/pressbooks-multi-institution.js"],"sourcesContent":["/* global context */\nimport \"../styles/pressbooks-multi-institutions.css\";\n\ndocument.addEventListener('DOMContentLoaded', function () {\n\tconst doAction = document.querySelector(`${context.formSelector} #doaction`);\n\n\tdoAction && doAction.addEventListener('click', function (e) {\n\t\te.preventDefault();\n\n\t\tconst action = document.querySelector(`${context.formSelector} #bulk-action-selector-top`).value;\n\t\tconst items = document.querySelectorAll(`${context.formSelector} .check-column input:checked`);\n\n\t\tif (action !== '-1' && items.length > 0 && confirm(context.confirmationMessage)) {\n\t\t\tdocument.querySelector(context.formSelector).submit();\n\t\t}\n\t});\n});\n"],"names":["doAction","e","action","items"],"mappings":"AAGA,SAAS,iBAAiB,mBAAoB,UAAY,CACzD,MAAMA,EAAW,SAAS,cAAc,GAAG,QAAQ,YAAY,aAAa,EAE5EA,GAAYA,EAAS,iBAAiB,QAAS,SAAUC,EAAG,CAC3DA,EAAE,eAAc,EAEhB,MAAMC,EAAS,SAAS,cAAc,GAAG,QAAQ,YAAY,4BAA4B,EAAE,MACrFC,EAAQ,SAAS,iBAAiB,GAAG,QAAQ,YAAY,8BAA8B,EAEzFD,IAAW,MAAQC,EAAM,OAAS,GAAK,QAAQ,QAAQ,mBAAmB,GAC7E,SAAS,cAAc,QAAQ,YAAY,EAAE,OAAM,CAEtD,CAAE,CACF,CAAC"} \ No newline at end of file +{"version":3,"file":"app-81345697.js","sources":["../../resources/assets/js/pressbooks-multi-institution.js"],"sourcesContent":["/* global context */\nimport \"../styles/pressbooks-multi-institutions.css\";\n\ndocument.addEventListener('DOMContentLoaded', function () {\n\tconst doAction = document.querySelector(`${context.formSelector} #doaction`);\n\n\tdoAction && doAction.addEventListener('click', function (e) {\n\t\te.preventDefault();\n\n\t\tconst action = document.querySelector(`${context.formSelector} #bulk-action-selector-top`).value;\n\t\tconst items = document.querySelectorAll(`${context.formSelector} .check-column input:checked`);\n\n\t\tif (action !== '-1' && items.length > 0 && confirm(context.confirmationMessage)) {\n\t\t\tdocument.querySelector(context.formSelector).submit();\n\t\t}\n\t});\n});\n"],"names":["doAction","e","action","items"],"mappings":"AAGA,SAAS,iBAAiB,mBAAoB,UAAY,CACzD,MAAMA,EAAW,SAAS,cAAc,GAAG,QAAQ,YAAY,aAAa,EAE5EA,GAAYA,EAAS,iBAAiB,QAAS,SAAUC,EAAG,CAC3DA,EAAE,eAAc,EAEhB,MAAMC,EAAS,SAAS,cAAc,GAAG,QAAQ,YAAY,4BAA4B,EAAE,MACrFC,EAAQ,SAAS,iBAAiB,GAAG,QAAQ,YAAY,8BAA8B,EAEzFD,IAAW,MAAQC,EAAM,OAAS,GAAK,QAAQ,QAAQ,mBAAmB,GAC7E,SAAS,cAAc,QAAQ,YAAY,EAAE,OAAM,CAEtD,CAAE,CACF,CAAC"} \ No newline at end of file diff --git a/dist/assets/pressbooks-multi-institution-34e9eba8.css b/dist/assets/pressbooks-multi-institution-34e9eba8.css new file mode 100644 index 000000000..1a6aca9e1 --- /dev/null +++ b/dist/assets/pressbooks-multi-institution-34e9eba8.css @@ -0,0 +1 @@ +:root{--odd-row-bg-color: #FFFFFF;--even-row-bg-color: #F6F7F7;--totals-row-bg-color: #dedede;--border-color: #e5e5e5;--totals-row-border-color: #C3C4C7;--pb-count-td-width: 10%;--pb-error-color: #d4002d;--pb-invalid-field-background-color: #fab;--table-text-color: #333}table.institutions #the-list tr:not(.totals-row):nth-child(odd){background-color:var(--odd-row-bg-color)}table.institutions #the-list tr:not(.totals-row):nth-child(2n){background-color:var(--even-row-bg-color)}table.institutions #the-list tr td,table.institutions #the-list tr th{border-bottom:1px solid var(--border-color);color:var(--table-text-color)}table.institutions #the-list tr td{margin:0 0 10px}table.institutions #the-list .row-title{font-size:13px!important}table.institutions #the-list .totals-row{background-color:var(--totals-row-bg-color)!important}table.institutions #the-list .totals-row td,table.institutions #the-list .totals-row th{border-bottom:1px solid var(--totals-row-border-color)}table.users tr td,table.assign-books tr td,table.users tr th,table.assign-books tr th{border-bottom:1px solid var(--border-color)}table.users tr td,table.assign-books tr td{margin:0 0 10px}@media screen and (min-width: 59.975rem){table.institutions-totals-table{width:50%}}h1.institutions-totals-heading{margin:20px 0}#pressbooks-multi-institution-assign-table .assign-books .column-cover{min-width:50px;text-align:center;width:5%}#pressbooks-multi-institution-assign-table .wp-list-table td{border-bottom:1px solid var(--border-color)}.striped .totals-row .text-right{padding-left:40px}body.institutions_page_pb_multi_institutions .filtering{display:flex;gap:10px;margin-top:10px}body.institutions_page_pb_multi_institutions .filtering ul{margin:0;padding-top:5px}body.institutions_page_pb_multi_institutions .filtering a{height:30px}.institutions_page_pb_multi_institution_form{--pb-input-width: 25em;--pb-selected-options-flex-direction: column}.institutions_page_pb_multi_institution_form .error .error-list,.institutions_page_pb_multi_institution_form .error li{margin:.5rem 0}.institutions_page_pb_multi_institution_form .error .padding{padding:0 2px}.institutions_page_pb_multi_institution_form .error .invalid{margin:.2rem 0 0 1rem}.institutions_page_pb_multi_institution_form .error .red{color:var(--pb-error-color)}.institutions_page_pb_multi_institution_form [aria-invalid]{background-color:var(--pb-invalid-field-background-color)}.institutions_page_pb_multi_institution_form .description{font-weight:400}.institutions_page_pb_multi_institution_form .form-table td select[hidden]{display:none}.institutions_page_pb_multi_institution_form .multiple-text-input{display:grid;grid-template-columns:1fr;gap:.5rem}.pb-institution-welcome-content p{margin:4rem 0 0!important}.pb-dashboard-content i.dashicons-before{margin-right:4px!important}form .form-table.institution th{width:260px}form .form-table.institution th,form .form-table.institution td{vertical-align:top;padding-top:10px}form .form-table.institution .institutional-managers-component{margin-top:-15px;display:flex}th#book_limit,th#users{width:var(--pb-count-td-width)}th#buy_in{width:65px} diff --git a/dist/assets/pressbooks-multi-institution-6c8c8aa3.css b/dist/assets/pressbooks-multi-institution-6c8c8aa3.css deleted file mode 100644 index 001daeea9..000000000 --- a/dist/assets/pressbooks-multi-institution-6c8c8aa3.css +++ /dev/null @@ -1 +0,0 @@ -:root{--odd-row-bg-color: #FFFFFF;--even-row-bg-color: #F6F7F7;--totals-row-bg-color: #dedede;--border-color: #e5e5e5;--totals-row-border-color: #C3C4C7;--pb-count-td-width: 10%;--pb-error-color: #d4002d;--pb-invalid-field-background-color: #fab;--table-text-color: #333}table.institutions #the-list tr:not(.totals-row):nth-child(odd){background-color:var(--odd-row-bg-color)}table.institutions #the-list tr:not(.totals-row):nth-child(2n){background-color:var(--even-row-bg-color)}table.institutions #the-list tr td,table.institutions #the-list tr th{border-bottom:1px solid var(--border-color);color:var(--table-text-color)}table.institutions #the-list tr td{margin:0 0 10px}table.institutions #the-list .row-title{font-size:13px!important}table.institutions #the-list .totals-row{background-color:var(--totals-row-bg-color)!important}table.institutions #the-list .totals-row td,table.institutions #the-list .totals-row th{border-bottom:1px solid var(--totals-row-border-color)}table.users tr td,table.assign-books tr td,table.users tr th,table.assign-books tr th{border-bottom:1px solid var(--border-color)}table.users tr td,table.assign-books tr td{margin:0 0 10px}#pressbooks-multi-institution-assign-table .assign-books .column-cover{min-width:50px;text-align:center;width:5%}#pressbooks-multi-institution-assign-table .wp-list-table td{border-bottom:1px solid var(--border-color)}.striped .totals-row .text-right{padding-left:40px}body.institutions_page_pb_multi_institutions .filtering{display:flex;gap:10px;margin-top:10px}body.institutions_page_pb_multi_institutions .filtering ul{margin:0;padding-top:5px}body.institutions_page_pb_multi_institutions .filtering a{height:30px}.institutions_page_pb_multi_institution_form{--pb-input-width: 25em;--pb-selected-options-flex-direction: column}.institutions_page_pb_multi_institution_form .error .error-list,.institutions_page_pb_multi_institution_form .error li{margin:.5rem 0}.institutions_page_pb_multi_institution_form .error .padding{padding:0 2px}.institutions_page_pb_multi_institution_form .error .invalid{margin:.2rem 0 0 1rem}.institutions_page_pb_multi_institution_form .error .red{color:var(--pb-error-color)}.institutions_page_pb_multi_institution_form [aria-invalid]{background-color:var(--pb-invalid-field-background-color)}.institutions_page_pb_multi_institution_form .description{font-weight:400}.institutions_page_pb_multi_institution_form .form-table td select[hidden]{display:none}.institutions_page_pb_multi_institution_form .multiple-text-input{display:grid;grid-template-columns:1fr;gap:.5rem}.pb-institution-welcome-content p{margin:4rem 0 0!important}.pb-dashboard-content i.dashicons-before{margin-right:4px!important}form .form-table.institution th{width:260px}form .form-table.institution th,form .form-table.institution td{vertical-align:top;padding-top:10px}form .form-table.institution .institutional-managers-component{margin-top:-15px;display:flex}th#book_limit,th#users{width:var(--pb-count-td-width)}th#buy_in{width:65px} diff --git a/dist/manifest.json b/dist/manifest.json index 0467d5c68..86c78184b 100644 --- a/dist/manifest.json +++ b/dist/manifest.json @@ -5,14 +5,14 @@ "src": "node_modules/@pressbooks/multiselect/pressbooks-multiselect.js" }, "resources/assets/js/pressbooks-multi-institution.css": { - "file": "assets/pressbooks-multi-institution-6c8c8aa3.css", + "file": "assets/pressbooks-multi-institution-34e9eba8.css", "src": "resources/assets/js/pressbooks-multi-institution.css" }, "resources/assets/js/pressbooks-multi-institution.js": { "css": [ - "assets/pressbooks-multi-institution-6c8c8aa3.css" + "assets/pressbooks-multi-institution-34e9eba8.css" ], - "file": "assets/app-f5d07064.js", + "file": "assets/app-81345697.js", "isEntry": true, "src": "resources/assets/js/pressbooks-multi-institution.js" } diff --git a/package-lock.json b/package-lock.json index 2b2b030a2..fdac34481 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,5 @@ { "name": "@pressbooks/pressbooks-multi-institution", - "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { @@ -39,9 +38,9 @@ } }, "node_modules/@csstools/selector-specificity": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.2.tgz", - "integrity": "sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.3.tgz", + "integrity": "sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==", "dev": true, "funding": [ { @@ -469,72 +468,6 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -617,20 +550,6 @@ "@esbuild/win32-x64": "0.18.20" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -646,78 +565,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -771,17 +618,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -798,24 +634,10 @@ "dev": true, "peer": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/postcss": { - "version": "8.4.34", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz", - "integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -835,16 +657,16 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-nesting": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.0.tgz", - "integrity": "sha512-QOYnosaZ+mlP6plQrAxFw09UUp2Sgtxj1BVHN+rSVbtV0Yx48zRt9/9F/ZOoxOKBBEsaJk2MYhhVRjeRRw5yuw==", + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.1.tgz", + "integrity": "sha512-qc74KvIAQNa5ujZKG1UV286dhaDW6basbUy2i9AzNU/T8C9hpvGu9NZzm1SfePe2yP7sPYgpA8d4sPVopn2Hhw==", "dev": true, "funding": [ { @@ -858,7 +680,7 @@ ], "dependencies": { "@csstools/selector-resolve-nested": "^1.1.0", - "@csstools/selector-specificity": "^3.0.2", + "@csstools/selector-specificity": "^3.0.3", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -881,20 +703,6 @@ "node": ">=4" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -912,25 +720,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/sass": { - "version": "1.71.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", - "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -953,29 +742,15 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "peer": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -983,9 +758,9 @@ "dev": true }, "node_modules/vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, "peer": true, "dependencies": { diff --git a/resources/assets/styles/pressbooks-multi-institutions.css b/resources/assets/styles/pressbooks-multi-institutions.css index 4513ed3d8..b627b06bb 100644 --- a/resources/assets/styles/pressbooks-multi-institutions.css +++ b/resources/assets/styles/pressbooks-multi-institutions.css @@ -58,6 +58,16 @@ table.users, table.assign-books { } } +@media screen and (min-width: 59.975rem) { + table.institutions-totals-table { + width: 50%; + } +} + +h1.institutions-totals-heading { + margin: 20px 0 20px; +} + #pressbooks-multi-institution-assign-table { .assign-books { .column-cover { diff --git a/resources/views/assign/index.blade.php b/resources/views/assign/index.blade.php index 2cc7f8e85..4a3804a9d 100644 --- a/resources/views/assign/index.blade.php +++ b/resources/views/assign/index.blade.php @@ -1,6 +1,16 @@ @if (isset($result['message'])) -
-

{{ $result['message'] }}

+
+
@endif diff --git a/resources/views/institutions/form.blade.php b/resources/views/institutions/form.blade.php index dc55b35d6..9ff1fc77d 100644 --- a/resources/views/institutions/form.blade.php +++ b/resources/views/institutions/form.blade.php @@ -1,21 +1,38 @@ @if (!empty($_POST) && isset($result['success']) && isset($result['message'])) -
-

- {{ $result['success'] ? __('Success', 'pressbooks-multi-institution') : __('Error', 'pressbooks-multi-institution') }}: - {{ $result['message'] }} -

+
+ - @if(isset($result['errors'])) +
@endif @@ -235,11 +252,15 @@ class="regular-text" + + {{ __('Only numbers are allowed.', 'pressbooks-multi-institution') }} + @endif diff --git a/resources/views/institutions/index.blade.php b/resources/views/institutions/index.blade.php index 924848642..7f4c04041 100644 --- a/resources/views/institutions/index.blade.php +++ b/resources/views/institutions/index.blade.php @@ -1,6 +1,16 @@ @if (isset($result['message'])) -
-

{{ $result['message'] }}

+
+
@endif @@ -29,4 +39,6 @@ {!! $table->display() !!} + + @include('PressbooksMultiInstitution::institutions.totals', ['totals' => $totals])
diff --git a/resources/views/institutions/rows/totals.blade.php b/resources/views/institutions/rows/totals.blade.php deleted file mode 100644 index da68af4b9..000000000 --- a/resources/views/institutions/rows/totals.blade.php +++ /dev/null @@ -1,5 +0,0 @@ - - {{ $item['name'] }} - {{ $item['book_total'] }} - {{ $item['user_total'] }} - diff --git a/resources/views/institutions/totals.blade.php b/resources/views/institutions/totals.blade.php new file mode 100644 index 000000000..97ee39328 --- /dev/null +++ b/resources/views/institutions/totals.blade.php @@ -0,0 +1,20 @@ +

{{ __('Book and User Totals', 'pressbooks-multi-institution') }}

+ + + + + + + + + + + @foreach ($totals as $total) + + + + + + @endforeach + +
{{ __('Type', 'pressbooks-multi-institution') }}{{ __('Books', 'pressbooks-multi-institution') }}{{ __('Users', 'pressbooks-multi-institution') }}
{{ $total['type'] }}{{ $total['book_total'] }}{{ $total['user_total'] }}
diff --git a/resources/views/table/book-admins.blade.php b/resources/views/table/book-admins.blade.php index 8ca0928a2..e980f84c0 100644 --- a/resources/views/table/book-admins.blade.php +++ b/resources/views/table/book-admins.blade.php @@ -4,6 +4,6 @@ {{ $admin->fullname ?: $admin->user_login }} {{ $admin->user_email }}
- {{ $admin->institution ?? __('Unassigned', 'pressbooks-multi-institution') }} + {{ $admin->institution ?? __('No institution assigned', 'pressbooks-multi-institution') }}
@endforeach diff --git a/src/Controllers/InstitutionsController.php b/src/Controllers/InstitutionsController.php index 6b2d817d9..27120673f 100644 --- a/src/Controllers/InstitutionsController.php +++ b/src/Controllers/InstitutionsController.php @@ -13,6 +13,8 @@ use PressbooksMultiInstitution\Views\InstitutionsTable; use PressbooksMultiInstitution\Support\ConvertEmptyStringsToNull; +use PressbooksMultiInstitution\Views\InstitutionsTotals; + use function Pressbooks\Admin\NetworkManagers\is_restricted; class InstitutionsController extends BaseController @@ -36,6 +38,7 @@ public function index(): string 'list_url' => network_admin_url('admin.php?page=pb_multi_institutions'), 'add_new_url' => network_admin_url('admin.php?page=pb_multi_institution_form&action=new'), 'table' => $this->table, + 'totals' => (new InstitutionsTotals(app('db')))->getTotals(), 'result' => $result, 'params' => [ 'searchQuery' => $_REQUEST['s'] ?? '', @@ -65,7 +68,7 @@ public function form(): string ]); } - protected function processBulkActions(): array + public function processBulkActions(): array { $action = $this->table->current_action(); @@ -100,7 +103,7 @@ protected function processBulkActions(): array ]; } - protected function save(bool $isSuperAdmin): array + public function save(bool $isSuperAdmin): array { if (! $_POST) { return [ @@ -198,7 +201,7 @@ protected function validate(array $data, ?int $id): array $errors['name'][] = __('The name field is required.', 'pressbooks-multi-institution'); } - if ($this->nameExists($data['name'])) { + if ($this->nameExists($data['name'], $id)) { $errors['name'][] = $this->renderView('partials.errors.duplicate-name', [ 'name' => $data['name'] ]); @@ -226,9 +229,12 @@ protected function validate(array $data, ?int $id): array return $errors; } - protected function nameExists(string $name): bool + protected function nameExists(?string $name, ?int $id): bool { - return Institution::query()->where('name', $name)->exists(); + return Institution::query() + ->where('name', $name) + ->when($id, fn (EloquentBuilder $query) => $query->where('id', '<>', $id)) + ->exists(); } protected function fetchInstitution(): Institution diff --git a/src/Views/InstitutionsTable.php b/src/Views/InstitutionsTable.php index fd490e773..9f8690121 100644 --- a/src/Views/InstitutionsTable.php +++ b/src/Views/InstitutionsTable.php @@ -2,7 +2,6 @@ namespace PressbooksMultiInstitution\Views; -use Illuminate\Database\Capsule\Manager; use Illuminate\Database\Eloquent\Relations\HasMany; use PressbooksMultiInstitution\Models\Institution; use WP_List_Table; @@ -161,7 +160,6 @@ public function get_bulk_actions(): array public function prepare_items(): void { - $networkLimit = get_option('pb_plan_settings_book_limit', null); $unlimitedNetwork = is_network_unlimited(); // Retrieve the paginated data using Eloquent @@ -207,72 +205,6 @@ public function prepare_items(): void ]; })->toArray(); - /** @var Manager $db */ - $db = app('db'); - - $prefix = $db->getDatabaseManager()->getTablePrefix(); - - $bookCounts = $db->table('blogs') - ->selectRaw("count(*) as total") - ->selectRaw("count(case when {$prefix}institutions.id is null then 1 end) as unassigned") - ->selectRaw("count(case when {$prefix}institutions.id is not null and {$prefix}institutions.buy_in = false then 1 end) as shared") - ->selectRaw("count(case when {$prefix}institutions.id is not null then 1 end) as assigned") - ->selectRaw("count(case when {$prefix}institutions.id is not null and {$prefix}institutions.buy_in = true then 1 end) as premium") - ->leftJoin('institutions_blogs', 'institutions_blogs.blog_id', '=', 'blogs.blog_id') - ->leftJoin('institutions', 'institutions.id', '=', 'institutions_blogs.institution_id') - ->where('blogs.blog_id', '<>', get_main_site_id()) - ->first(); - - $userCounts = $db->table('users') - ->selectRaw("count(*) as total") - ->selectRaw("count(case when {$prefix}institutions.id is null then 1 end) as unassigned") - ->selectRaw("count(case when {$prefix}institutions.id is not null and {$prefix}institutions.buy_in = false then 1 end) as shared") - ->selectRaw("count(case when {$prefix}institutions.id is not null then 1 end) as assigned") - ->selectRaw("count(case when {$prefix}institutions.id is not null and {$prefix}institutions.buy_in = true then 1 end) as premium") - ->leftJoin('institutions_users', 'institutions_users.user_id', '=', 'users.ID') - ->leftJoin('institutions', 'institutions.id', '=', 'institutions_users.institution_id') - ->first(); - - $this->items[] = [ - 'totals' => true, - 'name' => __('Unassigned', 'pressbooks-multi-institution'), - 'book_total' => $bookCounts->unassigned, - 'user_total' => $userCounts->unassigned, - ]; - - $sharedBookCount = $bookCounts->unassigned + $bookCounts->shared; - $sharedUserCount = $userCounts->unassigned + $userCounts->shared; - - if ($unlimitedNetwork) { - $sharedBookCount = $bookCounts->total . '/' . __('unlimited', 'pressbooks-multi-institution'); - $sharedUserCount = $userCounts->total; - } else { - $sharedBookCount .= $networkLimit ? '/' . $networkLimit : ''; - } - - $this->items[] = [ - 'totals' => true, - 'name' => __('Shared Network Totals', 'pressbooks-multi-institution'), - 'book_total' => $sharedBookCount, - 'user_total' => $sharedUserCount, - ]; - - if (! $unlimitedNetwork) { - $this->items[] = [ - 'totals' => true, - 'name' => __('Premium Member Totals', 'pressbooks-multi-institution'), - 'book_total' => $bookCounts->premium, - 'user_total' => $userCounts->premium, - ]; - } - - $this->items[] = [ - 'totals' => true, - 'name' => __('Total Items', 'pressbooks-multi-institution'), - 'book_total' => $bookCounts->total, - 'user_total' => $userCounts->total, - ]; - $this->set_pagination_args([ 'total_items' => $institutions->total(), 'per_page' => $this->paginationSize, diff --git a/src/Views/InstitutionsTotals.php b/src/Views/InstitutionsTotals.php new file mode 100644 index 000000000..f63bc6f3a --- /dev/null +++ b/src/Views/InstitutionsTotals.php @@ -0,0 +1,98 @@ +prefix = $this->db->getDatabaseManager()->getTablePrefix(); + } + + public function getTotals(): array + { + $bookCounts = $this->getBookCounts(); + + $userCounts = $this->getUserCounts(); + + $rows[] = [ + 'type' => __('Unassigned', 'pressbooks-multi-institution'), + 'book_total' => $bookCounts->{$this::UNASSIGNED_ALIAS}, + 'user_total' => $userCounts->{$this::UNASSIGNED_ALIAS}, + ]; + + $sharedBookCount = $bookCounts->{$this::UNASSIGNED_ALIAS} + $bookCounts->{$this::SHARED_ALIAS}; + $sharedUserCount = $userCounts->{$this::UNASSIGNED_ALIAS} + $userCounts->{$this::SHARED_ALIAS}; + + $networkLimit = get_option('pb_plan_settings_book_limit', null); + $unlimitedNetwork = is_network_unlimited(); + + if ($unlimitedNetwork) { + $sharedBookCount = $bookCounts->{$this::TOTAL_ALIAS} . '/' . __('unlimited', 'pressbooks-multi-institution'); + $sharedUserCount = $userCounts->{$this::TOTAL_ALIAS}; + } else { + $sharedBookCount .= $networkLimit ? '/' . $networkLimit : ''; + } + + $rows[] = [ + 'type' => __('Shared Network Totals', 'pressbooks-multi-institution'), + 'book_total' => $sharedBookCount, + 'user_total' => $sharedUserCount, + ]; + + if (! $unlimitedNetwork) { + $rows[] = [ + 'type' => __('Premium Member Totals', 'pressbooks-multi-institution'), + 'book_total' => $bookCounts->{$this::PREIMIUM_ALIAS}, + 'user_total' => $userCounts->{$this::PREIMIUM_ALIAS}, + ]; + } + + $rows[] = [ + 'type' => __('All Network totals', 'pressbooks-multi-institution'), + 'book_total' => $bookCounts->{$this::TOTAL_ALIAS}, + 'user_total' => $userCounts->{$this::TOTAL_ALIAS}, + ]; + + return $rows; + } + + private function getUserCounts(): object + { + return $this->db->table('users') + ->selectRaw("count(*) as " . $this::TOTAL_ALIAS) + ->selectRaw("count(case when {$this->prefix}institutions.id is null then 1 end) as " . $this::UNASSIGNED_ALIAS) + ->selectRaw("count(case when {$this->prefix}institutions.id is not null and {$this->prefix}institutions.buy_in = false then 1 end) as " . $this::SHARED_ALIAS) + ->selectRaw("count(case when {$this->prefix}institutions.id is not null and {$this->prefix}institutions.buy_in = true then 1 end) as " . $this::PREIMIUM_ALIAS) + ->leftJoin('institutions_users', 'institutions_users.user_id', '=', 'users.ID') + ->leftJoin('institutions', 'institutions.id', '=', 'institutions_users.institution_id') + ->first(); + } + + private function getBookCounts(): object + { + return $this->db->table('blogs') + ->selectRaw("count(*) as " . $this::TOTAL_ALIAS) + ->selectRaw("count(case when {$this->prefix}institutions.id is null then 1 end) as " . $this::UNASSIGNED_ALIAS) + ->selectRaw("count(case when {$this->prefix}institutions.id is not null and {$this->prefix}institutions.buy_in = false then 1 end) as " . $this::SHARED_ALIAS) + ->selectRaw("count(case when {$this->prefix}institutions.id is not null and {$this->prefix}institutions.buy_in = true then 1 end) as " . $this::PREIMIUM_ALIAS) + ->leftJoin('institutions_blogs', 'institutions_blogs.blog_id', '=', 'blogs.blog_id') + ->leftJoin('institutions', 'institutions.id', '=', 'institutions_blogs.institution_id') + ->where('blogs.blog_id', '<>', get_main_site_id()) + ->first(); + } +} diff --git a/tests/Feature/Controllers/InstitutionsControllerTest.php b/tests/Feature/Controllers/InstitutionsControllerTest.php index 9a021b0db..445f06330 100644 --- a/tests/Feature/Controllers/InstitutionsControllerTest.php +++ b/tests/Feature/Controllers/InstitutionsControllerTest.php @@ -78,17 +78,43 @@ public function it_saves_institution(): void public function it_does_not_save_duplicated_institution_name(): void { $this->createInstitution(); - $institutionName = Institution::query()->first()->name; - $_POST['name'] = $institutionName; + $_POST['name'] = 'Fake Institution'; $_POST['domains'] = ['pressbooks.test', 'institution.pressbooks.test']; $_REQUEST['_wpnonce'] = wp_create_nonce('pb_multi_institution_form'); - $form = $this->institutionsController->form(); + $response = $this->institutionsController->save(isSuperAdmin: true); - $this->assertEquals(1, Institution::all()->count()); - $this->assertStringContainsString('The form is invalid.', $form); - $this->assertStringContainsString("An institution with the name {$institutionName} already exists.", $form); - $this->assertStringContainsString('Please use a different name.', $form); + $expected = << + An institution with the name Fake Institution already exists. + Please use a different name. +

+ +HTML; + + $this->assertEquals(1, Institution::query()->count()); + $this->assertFalse($response['success']); + $this->assertEquals('The form is invalid.', $response['message']); + $this->assertEquals($expected, $response['errors']['name'][0]); + } + + /** + * @test + */ + public function it_updates_the_institution_when_using_the_same_name(): void + { + $institution = $this->createInstitution(); + + $_POST['name'] = 'Fake Institution'; + $_POST['ID'] = $institution->id; + + $_REQUEST['_wpnonce'] = wp_create_nonce('pb_multi_institution_form'); + + $response = $this->institutionsController->save(isSuperAdmin: true); + + $this->assertEquals(1, Institution::query()->count()); + $this->assertTrue($response['success']); + $this->assertEquals('Institution has been updated.', $response['message']); } } diff --git a/tests/Feature/Views/InstitutionsTotalsTest.php b/tests/Feature/Views/InstitutionsTotalsTest.php new file mode 100644 index 000000000..24333755a --- /dev/null +++ b/tests/Feature/Views/InstitutionsTotalsTest.php @@ -0,0 +1,103 @@ + ['ID']]); + + $this->createInstitutionsUsers(3, 10); + + InstitutionUser::query()->create([ + 'institution_id' => Institution::query()->first()->id, + 'user_id' => $users[0]->ID, + ]); + + update_option('pb_plan_settings_book_limit', 10); + + $expected = [ + [ + 'type' => 'Unassigned', + 'book_total' => 0, + 'user_total' => 0, + ], + [ + 'type' => 'Shared Network Totals', + 'book_total' => '0/10', + 'user_total' => 11, + ], + [ + 'type' => 'Premium Member Totals', + 'book_total' => 0, + 'user_total' => 0, + ], + [ + 'type' => 'All Network totals', + 'book_total' => 0, + 'user_total' => 11, + ], + ]; + + $this->assertEquals($expected, (new InstitutionsTotals(app('db')))->getTotals()); + } + + /** + * @test + */ + public function it_returns_totals(): void + { + $this->createInstitutionsUsers(3, 10); + + $institution = Institution::query()->first(); + $institution->update(['buy_in' => true]); + + $premiumUsers = $institution->users()->count(); + + $this->newUser(); + + $this->newBook(); + + update_option('pb_plan_settings_book_limit', 5); + + $expected = [ + [ + 'type' => 'Unassigned', + 'book_total' => 1, + 'user_total' => 2, + ], + [ + 'type' => 'Shared Network Totals', + 'book_total' => '1/5', + 'user_total' => 12 - $premiumUsers, + ], + [ + 'type' => 'Premium Member Totals', + 'book_total' => 0, + 'user_total' => $premiumUsers, + ], + [ + 'type' => 'All Network totals', + 'book_total' => 1, + 'user_total' => 12, + ], + ]; + + $this->assertEquals($expected, (new InstitutionsTotals(app('db')))->getTotals()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 206c689cc..749fee433 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace Tests; +use Illuminate\Database\Capsule\Manager; use PressbooksMultiInstitution\Bootstrap; use PressbooksMultiInstitution\Database\Migration; use WP_UnitTestCase; @@ -19,6 +20,12 @@ public function tearDown(): void { parent::tearDown(); + /** @var Manager $db */ + $db = app('db'); + + $db->table('users')->where('user_login', '<>', 'admin')->delete(); + $db->table('blogs')->where('blog_id', '<>', get_main_site_id())->delete(); + Migration::rollback(); } diff --git a/tests/Traits/CreatesModels.php b/tests/Traits/CreatesModels.php index bfd959424..039ab2bbc 100644 --- a/tests/Traits/CreatesModels.php +++ b/tests/Traits/CreatesModels.php @@ -22,10 +22,6 @@ protected function newUser(array $properties = []): int 'user_email' => $properties['user_email'] ?? 'johndoe@fakedomain.edu', ]; - $wpdb->delete($wpdb->users, [ - 'user_login' => $properties['user_login'], - ]); - $user = $this->factory()->user->create($properties); $wpdb->query('COMMIT'); @@ -72,10 +68,6 @@ protected function newBook(array $properties = []): int 'title' => $properties['title'] ?? 'Fake Book', ]; - $wpdb->delete($wpdb->blogs, [ - 'path' => "/{$properties['path']}/", - ]); - $blog = $this->factory()->blog->create($properties); if(!isset($properties['no_collector'])) {