From ee96f2080437ecd6963b44e803953da0dd13272b Mon Sep 17 00:00:00 2001 From: Tharindu Kuruppu Date: Sat, 23 Mar 2019 16:56:57 +0530 Subject: [PATCH] init --- .gitignore | 10 + composer.json | 43 +++ config/novapermissions.php | 83 +++++ .../migrations/create_gates_table.php.stub | 58 ++++ dist/css/tool.css | 0 dist/js/tool.js | 1 + dist/mix-manifest.json | 4 + package.json | 21 ++ readme.md | 302 ++++++++++++++++++ resources/js/components/DetailField.vue | 68 ++++ resources/js/components/FormField.vue | 145 +++++++++ resources/js/components/IndexField.vue | 30 ++ resources/js/tool.js | 6 + resources/sass/tool.scss | 1 + src/Checkboxes.php | 60 ++++ src/Nova/Role.php | 156 +++++++++ src/NovaPermissions.php | 23 ++ src/Permission.php | 53 +++ src/Policies/Policy.php | 19 ++ src/Providers/AuthServiceProvider.php | 40 +++ src/Role.php | 170 ++++++++++ src/ToolServiceProvider.php | 53 +++ src/Traits/HasRoles.php | 138 ++++++++ src/Traits/ValidatesPermissions.php | 25 ++ webpack.mix.js | 5 + 25 files changed, 1514 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 config/novapermissions.php create mode 100644 database/migrations/create_gates_table.php.stub create mode 100644 dist/css/tool.css create mode 100644 dist/js/tool.js create mode 100644 dist/mix-manifest.json create mode 100644 package.json create mode 100644 readme.md create mode 100644 resources/js/components/DetailField.vue create mode 100644 resources/js/components/FormField.vue create mode 100644 resources/js/components/IndexField.vue create mode 100644 resources/js/tool.js create mode 100644 resources/sass/tool.scss create mode 100644 src/Checkboxes.php create mode 100644 src/Nova/Role.php create mode 100644 src/NovaPermissions.php create mode 100644 src/Permission.php create mode 100644 src/Policies/Policy.php create mode 100644 src/Providers/AuthServiceProvider.php create mode 100644 src/Role.php create mode 100644 src/ToolServiceProvider.php create mode 100644 src/Traits/HasRoles.php create mode 100644 src/Traits/ValidatesPermissions.php create mode 100644 webpack.mix.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b817577 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/.idea +/vendor +/node_modules +package-lock.json +composer.phar +composer.lock +phpunit.xml +.phpunit.result.cache +.DS_Store +Thumbs.db diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9f5d69b --- /dev/null +++ b/composer.json @@ -0,0 +1,43 @@ +{ + "name": "pktharindu/nova-permissions", + "description": "Laravel Nova Grouped Permissions (ACL)", + "keywords": [ + "laravel", + "nova", + "tool", + "acl", + "roles", + "permissions", + "access control", + "gates", + "policies", + "authentication", + "authorization" + ], + "license": "MIT", + "authors": [{ + "name": "P. K. Tharindu", + "email": "pktharindu@outlook.com" + }], + "require": { + "php": ">=7.1.0", + "benjaminhirsch/nova-slug-field": "^1.1" + }, + "autoload": { + "psr-4": { + "Pktharindu\\NovaPermissions\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "Pktharindu\\NovaPermissions\\ToolServiceProvider" + ] + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/config/novapermissions.php b/config/novapermissions.php new file mode 100644 index 0000000..1a03f7c --- /dev/null +++ b/config/novapermissions.php @@ -0,0 +1,83 @@ + 'App\User', + + /* + |-------------------------------------------------------------------------- + | Nova User resource tool class + |-------------------------------------------------------------------------- + */ + + 'userResource' => 'App\Nova\User', + + /* + |-------------------------------------------------------------------------- + | The group associated with the resource + |-------------------------------------------------------------------------- + */ + + 'roleResourceGroup' => 'Other', + + /* + |-------------------------------------------------------------------------- + | Application Permissions + |-------------------------------------------------------------------------- + */ + + 'permissions' => [ + 'view users' => [ + 'display_name' => 'View users', + 'description' => 'Can view users', + 'group' => 'User', + ], + + 'create users' => [ + 'display_name' => 'Create users', + 'description' => 'Can create users', + 'group' => 'User', + ], + + 'edit users' => [ + 'display_name' => 'Edit users', + 'description' => 'Can edit users', + 'group' => 'User', + ], + + 'delete users' => [ + 'display_name' => 'Delete users', + 'description' => 'Can delete users', + 'group' => 'User', + ], + + 'view roles' => [ + 'display_name' => 'View roles', + 'description' => 'Can view roles', + 'group' => 'Role', + ], + + 'create roles' => [ + 'display_name' => 'Create roles', + 'description' => 'Can create roles', + 'group' => 'Role', + ], + + 'edit roles' => [ + 'display_name' => 'Edit roles', + 'description' => 'Can edit roles', + 'group' => 'Role', + ], + + 'delete roles' => [ + 'display_name' => 'Delete roles', + 'description' => 'Can delete roles', + 'group' => 'Role', + ], + ], +]; diff --git a/database/migrations/create_gates_table.php.stub b/database/migrations/create_gates_table.php.stub new file mode 100644 index 0000000..bd0b89e --- /dev/null +++ b/database/migrations/create_gates_table.php.stub @@ -0,0 +1,58 @@ +bigIncrements('id'); + $table->string('slug'); + $table->string('name')->nullable(); + $table->timestamps(); + }); + Schema::create('role_permission', function (Blueprint $table) { + $table->unsignedBigInteger('role_id'); + $table->string('permission_slug'); + $table->timestamps(); + $table->foreign('role_id') + ->references('id') + ->on('roles') + ->onDelete('cascade'); + $table->primary(['role_id', 'permission_slug']); + }); + Schema::create('role_user', function (Blueprint $table) { + $table->unsignedBigInteger('role_id'); + $table->unsignedBigInteger('user_id'); + $table->timestamps(); + $table->foreign('role_id') + ->references('id') + ->on('roles') + ->onDelete('cascade'); + + $table->foreign('user_id') + ->references('id') + ->on('users') + ->onDelete('cascade'); + $table->primary(['role_id', 'user_id']); + }); + } + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('role_permission'); + Schema::dropIfExists('role_user'); + Schema::dropIfExists('roles'); + } +} \ No newline at end of file diff --git a/dist/css/tool.css b/dist/css/tool.css new file mode 100644 index 0000000..e69de29 diff --git a/dist/js/tool.js b/dist/js/tool.js new file mode 100644 index 0000000..c715d08 --- /dev/null +++ b/dist/js/tool.js @@ -0,0 +1 @@ +!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t){e.exports=function(e,t,r,n,o,i){var u,s=e=e||{},a=typeof e.default;"object"!==a&&"function"!==a||(u=e,s=e.default);var c,f="function"==typeof s?s.options:s;if(t&&(f.render=t.render,f.staticRenderFns=t.staticRenderFns,f._compiled=!0),r&&(f.functional=!0),o&&(f._scopeId=o),i?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(i)},f._ssrRegister=c):n&&(c=n),c){var l=f.functional,p=l?f.render:f.beforeCreate;l?(f._injectStyles=c,f.render=function(e,t){return c.call(t),p(e,t)}):f.beforeCreate=p?[].concat(p,c):[c]}return{esModule:u,exports:s,options:f}}},function(e,t,r){r(2),e.exports=r(13)},function(e,t,r){Nova.booting(function(e,t){e.component("index-FieldCheckboxes",r(3)),e.component("detail-FieldCheckboxes",r(6)),e.component("form-FieldCheckboxes",r(9))})},function(e,t,r){var n=r(0)(r(4),r(5),!1,null,null,null);e.exports=n.exports},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:["resourceName","field"],computed:{availableOptions:function(){return _.flatMap(this.field.options)}},methods:{optionClass:function(e){return{"bg-success":!!this.field.value&&this.field.value.includes(e),"bg-danger":!this.field.value||!this.field.value.includes(e)}}}}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("span",{staticClass:"w-full"},e._l(e.availableOptions,function(t,n){return r("span",{key:t.option,staticClass:"inline-block rounded-full w-2 h-2 mr-1",class:e.optionClass(t.option),attrs:{title:t.label}})}),0)},staticRenderFns:[]}},function(e,t,r){var n=r(0)(r(7),r(8),!1,null,null,null);e.exports=n.exports},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:["resource","resourceName","resourceId","field"],methods:{optionClass:function(e){return{"bg-success":!!this.field.value&&this.field.value.includes(e),"bg-danger":!this.field.value||!this.field.value.includes(e)}}}}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("panel-item",{attrs:{field:e.field}},[r("p",{staticClass:"text-90 flex",attrs:{slot:"value"},slot:"value"},[e.field.withGroups?r("span",{staticClass:"w-full flex flex-wrap -mx-2"},e._l(e.field.options,function(t,n){return r("div",{key:n,staticClass:"mb-4 flex-auto mx-2"},[r("h3",{staticClass:"my-2"},[e._v(e._s(e.__(n)))]),e._v(" "),e._l(t,function(t,n){return r("div",{key:n,staticClass:"flex-auto",attrs:{title:t.description}},[r("span",{staticClass:"inline-block rounded-full w-2 h-2 mr-1",class:e.optionClass(t.option)}),e._v(" "),r("span",[e._v(e._s(t.label))])])})],2)}),0):r("span",{staticClass:"w-full flex flex-wrap -mx-2"},e._l(e.field.options,function(t,n){return r("div",{key:n,staticClass:"flex-auto mx-2",attrs:{title:t.description}},[r("span",{staticClass:"inline-block rounded-full w-2 h-2 mr-1",class:e.optionClass(n)}),e._v(" "),r("span",[e._v(e._s(t.label))])])}),0)])])},staticRenderFns:[]}},function(e,t,r){var n=r(0)(r(10),r(12),!1,null,null,null);e.exports=n.exports},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=r(11);r.n(n);t.default={mixins:[n.FormField,n.HandlesValidationErrors],props:["resourceName","resourceId","field"],methods:{checkAll:function(){if(this.field.withGroups)for(var e=_.flatMap(this.field.options),t=0;t=200&&e<300}};a.headers={common:{Accept:"application/json, text/plain, */*"}},n.forEach(["delete","get","head"],function(e){a.headers[e]={}}),n.forEach(["post","put","patch"],function(e){a.headers[e]=n.merge(i)}),e.exports=a}).call(t,r(75))},function(e,t,r){"use strict";t.__esModule=!0;var n,o=r(113),i=(n=o)&&n.__esModule?n:{default:n};t.default=function(e,t,r){return t in e?(0,i.default)(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,r){var n=r(9),o=r(1).document,i=n(o)&&n(o.createElement);e.exports=function(e){return i?o.createElement(e):{}}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){e.exports=!0},function(e,t,r){"use strict";var n=r(14);e.exports.f=function(e){return new function(e){var t,r;this.promise=new e(function(e,n){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=n}),this.resolve=n(t),this.reject=n(r)}(e)}},function(e,t,r){var n=r(11).f,o=r(17),i=r(2)("toStringTag");e.exports=function(e,t,r){e&&!o(e=r?e:e.prototype,i)&&n(e,i,{configurable:!0,value:t})}},function(e,t,r){var n=r(60)("keys"),o=r(65);e.exports=function(e){return n[e]||(n[e]=o(e))}},function(e,t){var r=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:r)(e)}},function(e,t,r){var n=r(54),o=r(27);e.exports=function(e){return n(o(e))}},function(e,t,r){var n=r(12).Symbol;e.exports=n},function(e,t,r){var n=r(170),o=r(189);e.exports=function(e,t){var r=o(e,t);return n(r)?r:void 0}},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t){e.exports=function(e){return e}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=["1/2","1/3","2/3","1/4","3/4","1/5","2/5","3/5","4/5","1/6","5/6"]},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=r(154);Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i(n).default}}),Object.defineProperty(t,"Form",{enumerable:!0,get:function(){return i(n).default}});var o=r(66);function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"Errors",{enumerable:!0,get:function(){return i(o).default}})},function(e,t,r){"use strict";(function(t){var n=r(0),o=r(101),i=r(104),u=r(110),s=r(108),a=r(45),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||r(103);e.exports=function(e){return new Promise(function(f,l){var p=e.data,h=e.headers;n.isFormData(p)&&delete h["Content-Type"];var d=new XMLHttpRequest,v="onreadystatechange",y=!1;if("test"===t.env.NODE_ENV||"undefined"==typeof window||!window.XDomainRequest||"withCredentials"in d||s(e.url)||(d=new window.XDomainRequest,v="onload",y=!0,d.onprogress=function(){},d.ontimeout=function(){}),e.auth){var g=e.auth.username||"",m=e.auth.password||"";h.Authorization="Basic "+c(g+":"+m)}if(d.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),d.timeout=e.timeout,d[v]=function(){if(d&&(4===d.readyState||y)&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var t="getAllResponseHeaders"in d?u(d.getAllResponseHeaders()):null,r={data:e.responseType&&"text"!==e.responseType?d.response:d.responseText,status:1223===d.status?204:d.status,statusText:1223===d.status?"No Content":d.statusText,headers:t,config:e,request:d};o(f,l,r),d=null}},d.onerror=function(){l(a("Network Error",e,null,d)),d=null},d.ontimeout=function(){l(a("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",d)),d=null},n.isStandardBrowserEnv()){var x=r(106),b=(e.withCredentials||s(e.url))&&e.xsrfCookieName?x.read(e.xsrfCookieName):void 0;b&&(h[e.xsrfHeaderName]=b)}if("setRequestHeader"in d&&n.forEach(h,function(e,t){void 0===p&&"content-type"===t.toLowerCase()?delete h[t]:d.setRequestHeader(t,e)}),e.withCredentials&&(d.withCredentials=!0),e.responseType)try{d.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&d.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){d&&(d.abort(),l(e),d=null)}),void 0===p&&(p=null),d.send(p)})}}).call(t,r(75))},function(e,t,r){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,r){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t,r){"use strict";var n=r(100);e.exports=function(e,t,r,o,i){var u=new Error(e);return n(u,t,r,o,i)}},function(e,t,r){"use strict";e.exports=function(e,t){return function(){for(var r=new Array(arguments.length),n=0;nr;)t.push(arguments[r++]);return g[++y]=function(){s("function"==typeof e?e:Function(e),t)},n(y),y},h=function(e){delete g[e]},"process"==r(15)(l)?n=function(e){l.nextTick(u(m,e,1))}:v&&v.now?n=function(e){v.now(u(m,e,1))}:d?(i=(o=new d).port2,o.port1.onmessage=x,n=u(i.postMessage,i,1)):f.addEventListener&&"function"==typeof postMessage&&!f.importScripts?(n=function(e){f.postMessage(e+"","*")},f.addEventListener("message",x,!1)):n="onreadystatechange"in c("script")?function(e){a.appendChild(c("script")).onreadystatechange=function(){a.removeChild(this),m.call(e)}}:function(e){setTimeout(u(m,e,1),0)}),e.exports={set:p,clear:h}},function(e,t,r){var n=r(34),o=Math.min;e.exports=function(e){return e>0?o(n(e),9007199254740991):0}},function(e,t,r){var n=r(27);e.exports=function(e){return Object(n(e))}},function(e,t){var r=0,n=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++r+n).toString(36))}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{};!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.record(t)}return n(e,[{key:"all",value:function(){return this.errors}},{key:"has",value:function(e){var t=this.errors.hasOwnProperty(e);t||(t=Object.keys(this.errors).filter(function(t){return t.startsWith(e+".")||t.startsWith(e+"[")}).length>0);return t}},{key:"first",value:function(e){return this.get(e)[0]}},{key:"get",value:function(e){return this.errors[e]||[]}},{key:"any",value:function(){return Object.keys(this.errors).length>0}},{key:"record",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.errors=e}},{key:"clear",value:function(e){if(e){var t=Object.assign({},this.errors);Object.keys(t).filter(function(t){return t===e||t.startsWith(e+".")||t.startsWith(e+"[")}).forEach(function(e){return delete t[e]}),this.errors=t}else this.errors={}}}]),e}();t.default=o},function(e,t,r){var n=r(177),o=r(229),i=r(13),u=r(230),s=r(70),a=r(231),c=Object.prototype.hasOwnProperty;e.exports=function(e,t){var r=i(e),f=!r&&o(e),l=!r&&!f&&u(e),p=!r&&!f&&!l&&a(e),h=r||f||l||p,d=h?n(e.length,String):[],v=d.length;for(var y in e)!t&&!c.call(e,y)||h&&("length"==y||l&&("offset"==y||"parent"==y)||p&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,v))||d.push(y);return d}},function(e,t,r){(function(t){var r="object"==typeof t&&t&&t.Object===Object&&t;e.exports=r}).call(t,r(241))},function(e,t){var r=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return r.test(e)}},function(e,t){var r=9007199254740991,n=/^(?:0|[1-9]\d*)$/;e.exports=function(e,t){var o=typeof e;return!!(t=null==t?r:t)&&("number"==o||"symbol"!=o&&n.test(e))&&e>-1&&e%1==0&&e-1&&e%1==0&&e<=r}},function(e,t,r){var n=r(178);e.exports=function(e){return null==e?"":n(e)}},function(e,t){var r,n,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function u(){throw new Error("clearTimeout has not been defined")}function s(e){if(r===setTimeout)return setTimeout(e,0);if((r===i||!r)&&setTimeout)return r=setTimeout,setTimeout(e,0);try{return r(e,0)}catch(t){try{return r.call(null,e,0)}catch(t){return r.call(this,e,0)}}}!function(){try{r="function"==typeof setTimeout?setTimeout:i}catch(e){r=i}try{n="function"==typeof clearTimeout?clearTimeout:u}catch(e){n=u}}();var a,c=[],f=!1,l=-1;function p(){f&&a&&(f=!1,a.length?c=a.concat(c):l=-1,c.length&&h())}function h(){if(!f){var e=s(p);f=!0;for(var t=c.length;t;){for(a=c,c=[];++l1)for(var r=1;r1&&void 0!==arguments[1]?arguments[1]:null;return this.viaManyToMany?this.detachResources(e):Nova.request({url:"/nova-api/"+this.resourceName,method:"delete",params:(0,i.default)({},this.queryString,{resources:u(e)})}).then(r||function(){t.deleteModalOpen=!1,t.getResources()})},deleteSelectedResources:function(){this.deleteResources(this.selectedResources)},deleteAllMatchingResources:function(){var e=this;return this.viaManyToMany?this.detachAllMatchingResources():Nova.request({url:this.deleteAllMatchingResourcesEndpoint,method:"delete",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){e.deleteModalOpen=!1,e.getResources()})},detachResources:function(e){var t=this;return Nova.request({url:"/nova-api/"+this.resourceName+"/detach",method:"delete",params:(0,i.default)({},this.queryString,{resources:u(e)})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},detachAllMatchingResources:function(){var e=this;return Nova.request({url:"/nova-api/"+this.resourceName+"/detach",method:"delete",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){e.deleteModalOpen=!1,e.getResources()})},forceDeleteResources:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return Nova.request({url:"/nova-api/"+this.resourceName+"/force",method:"delete",params:(0,i.default)({},this.queryString,{resources:u(e)})}).then(r||function(){t.deleteModalOpen=!1,t.getResources()})},forceDeleteSelectedResources:function(){this.forceDeleteResources(this.selectedResources)},forceDeleteAllMatchingResources:function(){var e=this;return Nova.request({url:this.forceDeleteSelectedResourcesEndpoint,method:"delete",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){e.deleteModalOpen=!1,e.getResources()})},restoreResources:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return Nova.request({url:"/nova-api/"+this.resourceName+"/restore",method:"put",params:(0,i.default)({},this.queryString,{resources:u(e)})}).then(r||function(){t.restoreModalOpen=!1,t.getResources()})},restoreSelectedResources:function(){this.restoreResources(this.selectedResources)},restoreAllMatchingResources:function(){var e=this;return Nova.request({url:this.restoreAllMatchingResourcesEndpoint,method:"put",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){e.restoreModalOpen=!1,e.getResources()})}},computed:{deleteAllMatchingResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens:"/nova-api/"+this.resourceName},forceDeleteSelectedResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens+"/force":"/nova-api/"+this.resourceName+"/force"},restoreAllMatchingResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens+"/restore":"/nova-api/"+this.resourceName+"/restore"},queryString:function(){return{search:this.currentSearch,filters:this.encodedFilters,trashed:this.currentTrashed,viaResource:this.viaResource,viaResourceId:this.viaResourceId,viaRelationship:this.viaRelationship}}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=u(r(50)),o=u(r(26)),i=u(r(49));u(r(226)),u(r(228));function u(e){return e&&e.__esModule?e:{default:e}}t.default={methods:{clearSelectedFilters:function(){var e=(0,i.default)(n.default.mark(function e(t){var r;return n.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(!t){e.next=5;break}return e.next=3,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName,lens:t});case 3:e.next=7;break;case 5:return e.next=7,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName});case 7:this.updateQueryString((r={},(0,o.default)(r,this.pageParameter,1),(0,o.default)(r,this.filterParameter,""),r));case 8:case"end":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}(),filterChanged:function(){var e;this.updateQueryString((e={},(0,o.default)(e,this.pageParameter,1),(0,o.default)(e,this.filterParameter,this.$store.getters[this.resourceName+"/currentEncodedFilters"]),e))},initializeFilters:function(){var e=(0,i.default)(n.default.mark(function e(t){return n.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return this.$store.commit(this.resourceName+"/clearFilters"),e.next=3,this.$store.dispatch(this.resourceName+"/fetchFilters",{resourceName:this.resourceName,lens:t});case 3:return e.next=5,this.initializeState(t);case 5:case"end":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}(),initializeState:function(){var e=(0,i.default)(n.default.mark(function e(t){return n.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(!this.initialEncodedFilters){e.next=5;break}return e.next=3,this.$store.dispatch(this.resourceName+"/initializeCurrentFilterValuesFromQueryString",this.initialEncodedFilters);case 3:e.next=7;break;case 5:return e.next=7,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName,lens:t});case 7:case"end":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}()},computed:{filterParameter:function(){return this.resourceName+"_filter"}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{resourceName:{},field:{}},data:function(){return{value:""}},mounted:function(){var e=this;this.setInitialValue(),this.field.fill=this.fill,Nova.$on(this.field.attribute+"-value",function(t){e.value=t})},destroyed:function(){Nova.$off(this.field.attribute+"-value")},methods:{setInitialValue:function(){this.value=void 0!==this.field.value&&null!==this.field.value?this.field.value:""},fill:function(e){e.append(this.field.attribute,String(this.value))},handleChange:function(e){this.value=e}},computed:{isReadonly:function(){return this.field.readonly||_.get(this.field,"extraAttributes.readonly")}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=r(41);t.default={props:{errors:{default:function(){return new n.Errors}}},data:function(){return{errorClass:"border-danger"}},computed:{errorClasses:function(){return this.hasError?[this.errorClass]:[]},fieldAttribute:function(){return this.field.attribute},hasError:function(){return this.errors.has(this.fieldAttribute)},firstError:function(){if(this.hasError)return this.errors.first(this.fieldAttribute)}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=u(r(50)),o=u(r(49)),i=u(r(40));function u(e){return e&&e.__esModule?e:{default:e}}t.default={props:{loadCards:{type:Boolean,default:!0}},data:function(){return{cards:[]}},created:function(){this.fetchCards()},watch:{cardsEndpoint:function(){this.fetchCards()}},methods:{fetchCards:function(){var e=(0,o.default)(n.default.mark(function e(){var t,r;return n.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(!this.loadCards){e.next=6;break}return e.next=3,Nova.request().get(this.cardsEndpoint,{params:this.extraCardParams});case 3:t=e.sent,r=t.data,this.cards=r;case 6:case"end":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},computed:{shouldShowCards:function(){return this.cards.length>0},smallCards:function(){return _.filter(this.cards,function(e){return-1!==i.default.indexOf(e.width)})},largeCards:function(){return _.filter(this.cards,function(e){return"full"==e.width})},extraCardParams:function(){return null}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={methods:{toAppTimezone:function(e){return e?moment.tz(e,this.userTimezone).clone().tz(Nova.config.timezone).format("YYYY-MM-DD HH:mm:ss"):e},fromAppTimezone:function(e){return e?moment.tz(e,Nova.config.timezone).clone().tz(this.userTimezone).format("YYYY-MM-DD HH:mm:ss"):e},localizeDateTimeField:function(e){if(!e.value)return e.value;var t=moment.tz(e.value,Nova.config.timezone).clone().tz(this.userTimezone);return e.format?t.format(e.format):this.usesTwelveHourTime?t.format("YYYY-MM-DD h:mm:ss A"):t.format("YYYY-MM-DD HH:mm:ss")},localizeDateField:function(e){if(!e.value)return e.value;var t=moment.tz(e.value,Nova.config.timezone).clone().tz(this.userTimezone);return e.format?t.format(e.format):t.format("YYYY-MM-DD")}},computed:{userTimezone:function(){return Nova.config.userTimezone?Nova.config.userTimezone:moment.tz.guess()},usesTwelveHourTime:function(){return _.endsWith((new Date).toLocaleString(),"AM")||_.endsWith((new Date).toLocaleString(),"PM")}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=r(225),i=(n=o)&&n.__esModule?n:{default:n};t.default={methods:{updateQueryString:function(e){this.$router.push({query:(0,i.default)(e,this.$route.query)})}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={computed:{resourceInformation:function(){var e=this;return _.find(Nova.config.resources,function(t){return t.uriKey==e.resourceName})},viaResourceInformation:function(){var e=this;if(this.viaResource)return _.find(Nova.config.resources,function(t){return t.uriKey==e.viaResource})},authorizedToCreate:function(){return this.resourceInformation.authorizedToCreate}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=r(26),i=(n=o)&&n.__esModule?n:{default:n};t.default={methods:{selectPreviousPage:function(){this.updateQueryString((0,i.default)({},this.pageParameter,this.currentPage-1))},selectNextPage:function(){this.updateQueryString((0,i.default)({},this.pageParameter,this.currentPage+1))}},computed:{currentPage:function(){return parseInt(this.$route.query[this.pageParameter]||1)}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=r(26),i=(n=o)&&n.__esModule?n:{default:n};t.default={data:function(){return{perPage:25}},methods:{initializePerPageFromQueryString:function(){this.perPage=this.currentPerPage},perPageChanged:function(){this.updateQueryString((0,i.default)({},this.perPageParameter,this.perPage))}},computed:{currentPerPage:function(){return this.$route.query[this.perPageParameter]||25}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=r(224),i=(n=o)&&n.__esModule?n:{default:n};t.default={data:function(){return{search:"",selectedResource:"",availableResources:[]}},methods:{selectResource:function(e){this.selectedResource=e},handleSearchCleared:function(){this.availableResources=[]},clearSelection:function(){this.selectedResource="",this.availableResources=[]},performSearch:function(e){var t=this;this.search=e;var r=e.trim();""!=r?this.debouncer(function(){t.selectedResource="",t.getAvailableResources(r)},500):this.clearSelection()},debouncer:(0,i.default)(function(e){return e()},500)}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={data:function(){return{withTrashed:!1}},methods:{toggleWithTrashed:function(){this.withTrashed=!this.withTrashed},enableWithTrashed:function(){this.withTrashed=!0},disableWithTrashed:function(){this.withTrashed=!1}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return(0,i.default)(e)};var n,o=r(238),i=(n=o)&&n.__esModule?n:{default:n}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=r(48),i=(n=o)&&n.__esModule?n:{default:n};t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;return i.default.all([e,new i.default(function(e){setTimeout(function(){return e()},t)})]).then(function(e){return e[0]})}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){return e>1||0==e?n.Inflector.pluralize(t):n.Inflector.singularize(t)};var n=r(47)},function(e,t,r){"use strict";var n={uncountableWords:["equipment","information","rice","money","species","series","fish","sheep","moose","deer","news"],pluralRules:[[new RegExp("(m)an$","gi"),"$1en"],[new RegExp("(pe)rson$","gi"),"$1ople"],[new RegExp("(child)$","gi"),"$1ren"],[new RegExp("^(ox)$","gi"),"$1en"],[new RegExp("(ax|test)is$","gi"),"$1es"],[new RegExp("(octop|vir)us$","gi"),"$1i"],[new RegExp("(alias|status)$","gi"),"$1es"],[new RegExp("(bu)s$","gi"),"$1ses"],[new RegExp("(buffal|tomat|potat)o$","gi"),"$1oes"],[new RegExp("([ti])um$","gi"),"$1a"],[new RegExp("sis$","gi"),"ses"],[new RegExp("(?:([^f])fe|([lr])f)$","gi"),"$1$2ves"],[new RegExp("(hive)$","gi"),"$1s"],[new RegExp("([^aeiouy]|qu)y$","gi"),"$1ies"],[new RegExp("(x|ch|ss|sh)$","gi"),"$1es"],[new RegExp("(matr|vert|ind)ix|ex$","gi"),"$1ices"],[new RegExp("([m|l])ouse$","gi"),"$1ice"],[new RegExp("(quiz)$","gi"),"$1zes"],[new RegExp("s$","gi"),"s"],[new RegExp("$","gi"),"s"]],singularRules:[[new RegExp("(m)en$","gi"),"$1an"],[new RegExp("(pe)ople$","gi"),"$1rson"],[new RegExp("(child)ren$","gi"),"$1"],[new RegExp("([ti])a$","gi"),"$1um"],[new RegExp("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$","gi"),"$1$2sis"],[new RegExp("(hive)s$","gi"),"$1"],[new RegExp("(tive)s$","gi"),"$1"],[new RegExp("(curve)s$","gi"),"$1"],[new RegExp("([lr])ves$","gi"),"$1f"],[new RegExp("([^fo])ves$","gi"),"$1fe"],[new RegExp("([^aeiouy]|qu)ies$","gi"),"$1y"],[new RegExp("(s)eries$","gi"),"$1eries"],[new RegExp("(m)ovies$","gi"),"$1ovie"],[new RegExp("(x|ch|ss|sh)es$","gi"),"$1"],[new RegExp("([m|l])ice$","gi"),"$1ouse"],[new RegExp("(bus)es$","gi"),"$1"],[new RegExp("(o)es$","gi"),"$1"],[new RegExp("(shoe)s$","gi"),"$1"],[new RegExp("(cris|ax|test)es$","gi"),"$1is"],[new RegExp("(octop|vir)i$","gi"),"$1us"],[new RegExp("(alias|status)es$","gi"),"$1"],[new RegExp("^(ox)en","gi"),"$1"],[new RegExp("(vert|ind)ices$","gi"),"$1ex"],[new RegExp("(matr)ices$","gi"),"$1ix"],[new RegExp("(quiz)zes$","gi"),"$1"],[new RegExp("s$","gi"),""]],nonTitlecasedWords:["and","or","nor","a","an","the","so","but","to","of","at","by","from","into","on","onto","off","out","in","over","with","for"],idSuffix:new RegExp("(_ids|_id)$","g"),underbar:new RegExp("_","g"),spaceOrUnderbar:new RegExp("[ _]","g"),uppercase:new RegExp("([A-Z])","g"),underbarPrefix:new RegExp("^_"),applyRules:function(e,t,r,n){if(n)e=n;else if(!(r.indexOf(e.toLowerCase())>-1))for(var o=0;o>8-s%1*8)){if((r=i.charCodeAt(s+=.75))>255)throw new o;t=t<<8|r}return u}},function(e,t,r){"use strict";var n=r(0);function o(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,r){if(!t)return e;var i;if(r)i=r(t);else if(n.isURLSearchParams(t))i=t.toString();else{var u=[];n.forEach(t,function(e,t){null!==e&&void 0!==e&&(n.isArray(e)?t+="[]":e=[e],n.forEach(e,function(e){n.isDate(e)?e=e.toISOString():n.isObject(e)&&(e=JSON.stringify(e)),u.push(o(t)+"="+o(e))}))}),i=u.join("&")}return i&&(e+=(-1===e.indexOf("?")?"?":"&")+i),e}},function(e,t,r){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,r){"use strict";var n=r(0);e.exports=n.isStandardBrowserEnv()?{write:function(e,t,r,o,i,u){var s=[];s.push(e+"="+encodeURIComponent(t)),n.isNumber(r)&&s.push("expires="+new Date(r).toGMTString()),n.isString(o)&&s.push("path="+o),n.isString(i)&&s.push("domain="+i),!0===u&&s.push("secure"),document.cookie=s.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},function(e,t,r){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t,r){"use strict";var n=r(0);e.exports=n.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),r=document.createElement("a");function o(e){var n=e;return t&&(r.setAttribute("href",n),n=r.href),r.setAttribute("href",n),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:"/"===r.pathname.charAt(0)?r.pathname:"/"+r.pathname}}return e=o(window.location.href),function(t){var r=n.isString(t)?o(t):t;return r.protocol===e.protocol&&r.host===e.host}}():function(){return!0}},function(e,t,r){"use strict";var n=r(0);e.exports=function(e,t){n.forEach(e,function(r,n){n!==t&&n.toUpperCase()===t.toUpperCase()&&(e[t]=r,delete e[n])})}},function(e,t,r){"use strict";var n=r(0),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,r,i,u={};return e?(n.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=n.trim(e.substr(0,i)).toLowerCase(),r=n.trim(e.substr(i+1)),t){if(u[t]&&o.indexOf(t)>=0)return;u[t]="set-cookie"===t?(u[t]?u[t]:[]).concat([r]):u[t]?u[t]+", "+r:r}}),u):u}},function(e,t,r){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},function(e,t,r){e.exports={default:r(115),__esModule:!0}},function(e,t,r){e.exports={default:r(116),__esModule:!0}},function(e,t,r){"use strict";t.__esModule=!0;var n,o=r(112),i=(n=o)&&n.__esModule?n:{default:n};t.default=i.default||function(e){for(var t=1;tf;)if((s=a[f++])!=s)return!0}else for(;c>f;f++)if((e||f in a)&&a[f]===r)return e||f||0;return!e&&-1}}},function(e,t,r){var n=r(16),o=r(125),i=r(124),u=r(4),s=r(63),a=r(144),c={},f={};(t=e.exports=function(e,t,r,l,p){var h,d,v,y,g=p?function(){return e}:a(e),m=n(r,l,t?2:1),x=0;if("function"!=typeof g)throw TypeError(e+" is not iterable!");if(i(g)){for(h=s(e.length);h>x;x++)if((y=t?m(u(d=e[x])[0],d[1]):m(e[x]))===c||y===f)return y}else for(v=g.call(e);!(d=v.next()).done;)if((y=o(v,m,d.value,t))===c||y===f)return y}).BREAK=c,t.RETURN=f},function(e,t,r){e.exports=!r(5)&&!r(29)(function(){return 7!=Object.defineProperty(r(28)("div"),"a",{get:function(){return 7}}).a})},function(e,t){e.exports=function(e,t,r){var n=void 0===r;switch(t.length){case 0:return n?e():e.call(r);case 1:return n?e(t[0]):e.call(r,t[0]);case 2:return n?e(t[0],t[1]):e.call(r,t[0],t[1]);case 3:return n?e(t[0],t[1],t[2]):e.call(r,t[0],t[1],t[2]);case 4:return n?e(t[0],t[1],t[2],t[3]):e.call(r,t[0],t[1],t[2],t[3])}return e.apply(r,t)}},function(e,t,r){var n=r(10),o=r(2)("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(n.Array===e||i[o]===e)}},function(e,t,r){var n=r(4);e.exports=function(e,t,r,o){try{return o?t(n(r)[0],r[1]):t(r)}catch(t){var i=e.return;throw void 0!==i&&n(i.call(e)),t}}},function(e,t,r){"use strict";var n=r(131),o=r(59),i=r(32),u={};r(7)(u,r(2)("iterator"),function(){return this}),e.exports=function(e,t,r){e.prototype=n(u,{next:o(1,r)}),i(e,t+" Iterator")}},function(e,t,r){var n=r(2)("iterator"),o=!1;try{var i=[7][n]();i.return=function(){o=!0},Array.from(i,function(){throw 2})}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var r=!1;try{var i=[7],u=i[n]();u.next=function(){return{done:r=!0}},i[n]=function(){return u},e(i)}catch(e){}return r}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,r){var n=r(1),o=r(62).set,i=n.MutationObserver||n.WebKitMutationObserver,u=n.process,s=n.Promise,a="process"==r(15)(u);e.exports=function(){var e,t,r,c=function(){var n,o;for(a&&(n=u.domain)&&n.exit();e;){o=e.fn,e=e.next;try{o()}catch(n){throw e?r():t=void 0,n}}t=void 0,n&&n.enter()};if(a)r=function(){u.nextTick(c)};else if(!i||n.navigator&&n.navigator.standalone)if(s&&s.resolve){var f=s.resolve(void 0);r=function(){f.then(c)}}else r=function(){o.call(n,c)};else{var l=!0,p=document.createTextNode("");new i(c).observe(p,{characterData:!0}),r=function(){p.data=l=!l}}return function(n){var o={fn:n,next:void 0};t&&(t.next=o),e||(e=o,r()),t=o}}},function(e,t,r){"use strict";var n=r(56),o=r(133),i=r(136),u=r(64),s=r(54),a=Object.assign;e.exports=!a||r(29)(function(){var e={},t={},r=Symbol(),n="abcdefghijklmnopqrst";return e[r]=7,n.split("").forEach(function(e){t[e]=e}),7!=a({},e)[r]||Object.keys(a({},t)).join("")!=n})?function(e,t){for(var r=u(e),a=arguments.length,c=1,f=o.f,l=i.f;a>c;)for(var p,h=s(arguments[c++]),d=f?n(h).concat(f(h)):n(h),v=d.length,y=0;v>y;)l.call(h,p=d[y++])&&(r[p]=h[p]);return r}:a},function(e,t,r){var n=r(4),o=r(132),i=r(52),u=r(33)("IE_PROTO"),s=function(){},a=function(){var e,t=r(28)("iframe"),n=i.length;for(t.style.display="none",r(53).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" \ No newline at end of file diff --git a/resources/js/components/FormField.vue b/resources/js/components/FormField.vue new file mode 100644 index 0000000..0022e02 --- /dev/null +++ b/resources/js/components/FormField.vue @@ -0,0 +1,145 @@ + + + \ No newline at end of file diff --git a/resources/js/components/IndexField.vue b/resources/js/components/IndexField.vue new file mode 100644 index 0000000..e5af256 --- /dev/null +++ b/resources/js/components/IndexField.vue @@ -0,0 +1,30 @@ + + + \ No newline at end of file diff --git a/resources/js/tool.js b/resources/js/tool.js new file mode 100644 index 0000000..e256d7c --- /dev/null +++ b/resources/js/tool.js @@ -0,0 +1,6 @@ +Nova.booting((Vue, router) => { + Vue.component('index-FieldCheckboxes', require('./components/IndexField')); + Vue.component('detail-FieldCheckboxes', require('./components/DetailField')); + Vue.component('form-FieldCheckboxes', require('./components/FormField')); + +}) diff --git a/resources/sass/tool.scss b/resources/sass/tool.scss new file mode 100644 index 0000000..f85ad40 --- /dev/null +++ b/resources/sass/tool.scss @@ -0,0 +1 @@ +// Nova Tool CSS diff --git a/src/Checkboxes.php b/src/Checkboxes.php new file mode 100644 index 0000000..94b67cf --- /dev/null +++ b/src/Checkboxes.php @@ -0,0 +1,60 @@ +withMeta(['options' => $options]); + } + + /** + * Disable type casting of array keys to numeric values to return the unmodified keys. + */ + public function withGroups() + { + return $this->withMeta(['withGroups' => true]); + } + + /** + * Hydrate the given attribute on the model based on the incoming request. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param string $requestAttribute + * @param object $model + * @param string $attribute + */ + protected function fillAttributeFromRequest(NovaRequest $request, $requestAttribute, $model, $attribute) + { + if ($request->exists($requestAttribute)) { + /* + * When editing entries, they are returned as comma seperated string (unsure why). + * As a result we need to include this check and explode the values if required. + */ + if (! \is_array($choices = $request[$requestAttribute])) { + $permissions = collect(explode(',', $choices))->reject(function ($name) { + return empty($name); + })->all(); + } + $model->setPermissions($permissions); + } + } +} diff --git a/src/Nova/Role.php b/src/Nova/Role.php new file mode 100644 index 0000000..4d23a29 --- /dev/null +++ b/src/Nova/Role.php @@ -0,0 +1,156 @@ +sortable(), + + TextWithSlug::make(__('Name'), 'name') + ->rules('required') + ->sortable() + ->slug(__('Slug')), + + Slug::make(__('Slug'), 'slug') + ->rules('required') + ->creationRules('unique:roles') + ->updateRules('unique:roles,slug,{{resourceId}}') + ->sortable(), + + Checkboxes::make(__('Permissions'), 'permissions') + ->withGroups() + ->options(collect(config('novapermissions.permissions'))->map(function ($permission, $key) { + return [ + 'group' => ucfirst($permission['group']), + 'option' => $key, + 'label' => $permission['display_name'], + 'description' => $permission['description'], + ]; + })->groupBy('group')->toArray()), + + Text::make(__('Users'), function () { + return \count($this->users); + })->onlyOnIndex(), + + BelongsToMany::make(__('Users'), 'users', config('novapermissions.userResource', 'App\Nova\User')) + ->searchable(), + ]; + } + + /** + * Get the filters available for the resource. + * + * @param \Illuminate\Http\Request $request + * + * @return array + */ + public function filters(Request $request) + { + return []; + } + + public static function label() + { + return __('Roles'); + } + + /** + * Get the lenses available for the resource. + * + * @param \Illuminate\Http\Request $request + * + * @return array + */ + public function lenses(Request $request) + { + return []; + } + + public static function singularLabel() + { + return __('Role'); + } +} diff --git a/src/NovaPermissions.php b/src/NovaPermissions.php new file mode 100644 index 0000000..d941a56 --- /dev/null +++ b/src/NovaPermissions.php @@ -0,0 +1,23 @@ +belongsToMany(Role::class, 'role_permission', 'permission_slug', 'role_id'); + } + + /** + * Determine if any users have access to this permission. + * + * @return boolean + */ + public function hasUsers() + { + return (bool) $this->roles()->has('users')->count(); + } +} diff --git a/src/Policies/Policy.php b/src/Policies/Policy.php new file mode 100644 index 0000000..99191e7 --- /dev/null +++ b/src/Policies/Policy.php @@ -0,0 +1,19 @@ +registerPolicies(); + $this->defineGates(); + } + + private function defineGates() + { + foreach (config('novapermissions.permissions') as $key => $permissions) { + Gate::define($key, function (User $user) use ($key) { + if ($this->nobodyHasAccess($key)) { + return true; + } + + return $user->hasPermissionTo($key); + }); + } + } +} diff --git a/src/Role.php b/src/Role.php new file mode 100644 index 0000000..a9d2095 --- /dev/null +++ b/src/Role.php @@ -0,0 +1,170 @@ + 'array', + ]; + + /** + * Get all users which are assigned a specific role. + * + * @return Illuminate\Support\Collection + */ + public function users() + { + return $this->belongsToMany(config('novapermissions.userModel', 'App\User')); + } + + /** + * Returns all Permissions for this Role. + * + * @return Illuminate\Support\Collection + */ + public function getPermissions() + { + return $this->hasMany(Permission::class); + } + + /** + * Replace all existing permissions with a new set of permissions. + * + * @param array $permissions + */ + public function setPermissions(array $permissions) + { + if (! $this->id) { + $this->save(); + } + + $this->revokeAll(); + + collect($permissions)->map(function ($permission) { + $this->grant($permission); + }); + } + + /** + * Check if a user has a given permission. + * + * @param string $permission + * + * @return bool + */ + public function hasPermission($permission) + { + return $this->getPermissions->contains('permission_slug', $permission); + } + + /** + * Give Permission to a Role. + * + * @param string $permission + * + * @return bool + */ + public function grant($permission) + { + if ($this->hasPermission($permission)) { + return true; + } + + if (! array_key_exists($permission, Gate::abilities())) { + abort(403, 'Unknown permission'); + } + + return Permission::create([ + 'role_id' => $this->id, + 'permission_slug' => $permission, + ]); + + return false; + } + + /** + * Revokes a Permission from a Role. + * + * @param string $permission + * + * @return bool + */ + public function revoke($permission) + { + if (\is_string($permission)) { + return Permission::findOrFail($permission)->delete(); + } + + return false; + } + + /** + * Remove all permissions from this Role. + */ + public function revokeAll() + { + return $this->getPermissions()->delete(); + } + + /** + * Get a list of permissions. + * + * @return array + */ + public function getPermissionsAttribute() + { + return Permission::where('role_id', $this->id)->get()->pluck('permission_slug')->toArray(); + } + + /** + * Replace all existing permissions with a new set of permissions. + * + * @param array $permissions + */ + public function setPermissionsAttribute(array $permissions) + { + if (! $this->id) { + $this->save(); + } + + $this->revokeAll(); + + collect($permissions)->map(function ($permission) { + if (! \in_array($permission, Policy::all(), true)) { + return; + } + + $this->grant($permission); + }); + } +} diff --git a/src/ToolServiceProvider.php b/src/ToolServiceProvider.php new file mode 100644 index 0000000..e6bd5a3 --- /dev/null +++ b/src/ToolServiceProvider.php @@ -0,0 +1,53 @@ +publishes([ + __DIR__.'/../config/novapermissions.php' => config_path('novapermissions.php'), + ], 'config'); + + $this->publishes([ + __DIR__.'/../database/migrations/create_gates_table.php.stub' => $this->getMigrationFileName($filesystem), + ], 'migrations'); + } + + /** + * Register the application services. + */ + public function register() + { + $this->mergeConfigFrom( + __DIR__.'/../config/novapermissions.php', + 'novapermissions' + ); + } + + /** + * Returns existing migration file if found, else uses the current timestamp. + * + * @param Filesystem $filesystem + * + * @return string + */ + protected function getMigrationFileName(Filesystem $filesystem): string + { + $timestamp = date('Y_m_d_His'); + + return Collection::make($this->app->databasePath().\DIRECTORY_SEPARATOR.'migrations'.\DIRECTORY_SEPARATOR) + ->flatMap(function ($path) use ($filesystem) { + return $filesystem->glob($path.'*_create_gates_table.php'); + })->push($this->app->databasePath()."/migrations/{$timestamp}_create_gates_table.php") + ->first(); + } +} diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php new file mode 100644 index 0000000..aa0341d --- /dev/null +++ b/src/Traits/HasRoles.php @@ -0,0 +1,138 @@ +belongsToMany(Role::class)->with('getPermissions'); + } + + /** + * Scope a query to eager load `roles` relationship + * to reduce database queries. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeWithRoles($query) + { + return $query->with('roles'); + } + + /** + * Determine if any of the assigned roles to this user + * have a specific permission. + * + * @param string $permission + * + * @return bool + */ + public function hasPermissionTo($permission) + { + return $this->roles->contains(function ($role) use ($permission) { + return $role->getPermissions->contains('permission_slug', $permission); + }); + } + + /** + * Determine if the model has any of the given permissions. + * + * @param array ...$permissions + * + * @throws \Exception + * + * @return bool + */ + public function hasAnyPermission(...$permissions): bool + { + if (\is_array($permissions[0])) { + $permissions = $permissions[0]; + } + + foreach ($permissions as $permission) { + if ($this->hasPermissionTo($permission)) { + return true; + } + } + + return false; + } + + /** + * Determine if the model has all of the given permissions. + * + * @param array ...$permissions + * + * @throws \Exception + * + * @return bool + */ + public function hasAllPermissions(...$permissions): bool + { + if (\is_array($permissions[0])) { + $permissions = $permissions[0]; + } + + foreach ($permissions as $permission) { + if (! $this->hasPermissionTo($permission)) { + return false; + } + } + + return true; + } + + /** + * Assign a role to this user. + * + * @param string|Role $role + * + * @return bool + */ + public function assignRole($role) + { + if (\is_string($role)) { + return $this->roles()->attach(Role::where('slug', $role)->first()); + } + + return $this->roles()->attach($role); + } + + /** + * Remove a role from this user. + * + * @param string|Role $role + * + * @return bool + */ + public function removeRole($role) + { + if (\is_string($role)) { + return $this->roles()->detach(Role::where('slug', $role)->first()); + } + + return $this->roles()->detach($role); + } + + /** + * Reassign roles from an id or an array of role Ids. + * + * @param int|array $roles + */ + public function setRolesById($roles) + { + $roles = \is_array($roles) ? $roles : [$roles]; + + return $this->roles()->sync($roles); + } +} diff --git a/src/Traits/ValidatesPermissions.php b/src/Traits/ValidatesPermissions.php new file mode 100644 index 0000000..38053de --- /dev/null +++ b/src/Traits/ValidatesPermissions.php @@ -0,0 +1,25 @@ +hasUsers(); + } +} diff --git a/webpack.mix.js b/webpack.mix.js new file mode 100644 index 0000000..5bd2b46 --- /dev/null +++ b/webpack.mix.js @@ -0,0 +1,5 @@ +let mix = require('laravel-mix') + +mix.setPublicPath('dist') + .js('resources/js/tool.js', 'js') + .sass('resources/sass/tool.scss', 'css')