├── package.json ├── yarn.lock ├── src ├── web │ └── assets │ │ ├── out.css.map │ │ ├── OutIndexAsset.php │ │ ├── OutAsset.php │ │ ├── out.css │ │ ├── OutIndex.min.js │ │ ├── OutIndex.min.js.map │ │ └── OutEdit.min.js ├── templates │ ├── index.twig │ ├── settings.twig │ └── _edit.twig ├── models │ └── Settings.php ├── elements │ ├── db │ │ └── ExportQuery.php │ └── Export.php ├── base │ ├── IntegrationInterface.php │ └── Integrations.php ├── icon.svg ├── migrations │ └── Install.php ├── icon-mask.svg ├── integrations │ └── SproutFormsIntegration.php ├── Out.php ├── services │ └── OutService.php └── controllers │ └── OutController.php └── composer.json /package.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/web/assets/out.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../resources/less/out.less"],"names":[],"mappings":"AAEC,cACC,0BADF,CAMG,oCACC,oBAJJ,CAOG,0CACC,uBALJ,CAUC,WACC,yBAAA,CAEA,0BATF,CAMC,gBAME,oBAAA,CACA,eAAA,CAEA,aAAA,CACA,cAAA,CACA,eAAA,CACA,gBAAA,CACA,wBAAA,CAEA,kBAAA,CACA,iBAXH,CAeC,YACC,qBAAA,CACA,qBAAA,CACA,sBAAA,CACA,eAbF,CASC,mBAOE,cAbH,CAMC,uCAYE,UAdH","file":"out.css"} -------------------------------------------------------------------------------- /src/templates/index.twig: -------------------------------------------------------------------------------- 1 | {% extends '_layouts/elementindex' %} 2 | {% set title = pluginName %} 3 | {% set elementType = 'ether\\out\\elements\\Export' %} 4 | 5 | {% block actionButton %} 6 | {% if craft.app.user.checkPermission('out_createExport') or craft.app.user.isAdmin %} 7 | New {{ exportName }} 8 | {% endif %} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/models/Settings.php: -------------------------------------------------------------------------------- 1 | sourcePath = __DIR__; 14 | 15 | $this->depends = [ 16 | CpAsset::class, 17 | ]; 18 | 19 | $this->js = [ 20 | 'OutIndex.min.js', 21 | ]; 22 | 23 | parent::init(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/web/assets/OutAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = __DIR__; 14 | 15 | $this->depends = [ 16 | CpAsset::class, 17 | ]; 18 | 19 | $this->js = [ 20 | 'OutEdit.min.js', 21 | ]; 22 | 23 | $this->css = [ 24 | 'out.css', 25 | ]; 26 | 27 | parent::init(); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/web/assets/out.css: -------------------------------------------------------------------------------- 1 | .out--warning{padding:0 0 12px!important}.out--columns .settings.icon:before{color:rgba(0,0,0,.2)}.out--columns .settings.icon:hover:before{color:#0d78f2!important}.out--type{text-align:left!important;border-left:none!important}.out--type span{display:inline-block;padding:1px 7px;color:#989ea4;font-size:10px;font-weight:700;letter-spacing:0;text-transform:uppercase;background:#f1f5f8;border-radius:3px}.out--modal{height:auto!important;min-width:0!important;min-height:0!important;max-width:500px}.out--modal button{font-size:100%}.out--modal .select,.out--modal select{width:100%} 2 | /*# sourceMappingURL=out.css.map */ -------------------------------------------------------------------------------- /src/templates/settings.twig: -------------------------------------------------------------------------------- 1 | {% import "_includes/forms" as forms %} 2 | 3 | {{ forms.textField({ 4 | first: true, 5 | label: "Plugin Name", 6 | name: 'pluginName', 7 | value: settings.pluginName, 8 | instructions: 'Visually change the name of the plugin', 9 | }) }} 10 | 11 | {{ forms.textField({ 12 | first: true, 13 | label: "Export Name", 14 | name: 'exportName', 15 | value: settings.exportName, 16 | instructions: 'Visually change the name of the exports. This should be singular.', 17 | }) }} 18 | 19 | {{ forms.textField({ 20 | first: true, 21 | label: "Rows Per-file", 22 | name: 'split', 23 | type: 'number', 24 | value: settings.split, 25 | instructions: 'Split into multiple files every X rows', 26 | }) }} 27 | -------------------------------------------------------------------------------- /src/elements/db/ExportQuery.php: -------------------------------------------------------------------------------- 1 | joinElementTable('out_exports'); 27 | 28 | $this->query->select([ 29 | 'out_exports.title', 30 | 'out_exports.elementType', 31 | 'out_exports.elementSource', 32 | 'out_exports.order', 33 | 'out_exports.search', 34 | 'out_exports.limit', 35 | 'out_exports.startDate', 36 | 'out_exports.endDate', 37 | 'out_exports.fieldSettings', 38 | ]); 39 | 40 | return parent::beforePrepare(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ether/out", 3 | "description": "A super-simple plugin for creating CSV exports of you Craft data", 4 | "type": "craft-plugin", 5 | "version": "3.0.7", 6 | "keywords": [ 7 | "craft", 8 | "cms", 9 | "craftcms", 10 | "craft-plugin", 11 | "csv", 12 | "export" 13 | ], 14 | "support": { 15 | "docs": "https://github.com/ethercreative/out/blob/v3/README.md", 16 | "issues": "https://github.com/ethercreative/out/issues" 17 | }, 18 | "license": "proprietary", 19 | "authors": [ 20 | { 21 | "name": "Ether Creative", 22 | "homepage": "https://ethercreative.co.uk" 23 | } 24 | ], 25 | "require": { 26 | "craftcms/cms": "^3.0.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "ether\\out\\": "src/" 31 | } 32 | }, 33 | "extra": { 34 | "name": "Out", 35 | "handle": "out", 36 | "changelogUrl": "https://raw.githubusercontent.com/ethercreative/out/v3/CHANGELOG.md", 37 | "class": "ether\\out\\Out" 38 | } 39 | } -------------------------------------------------------------------------------- /src/base/IntegrationInterface.php: -------------------------------------------------------------------------------- 1 | [ 22 | * 'name' => 'Field Name', 23 | * 'handle' => 'Field Handle', 24 | * ] 25 | * ]; 26 | * ``` 27 | * 28 | * @return array 29 | */ 30 | public static function fields (): array; 31 | 32 | /** 33 | * Returns true if the plugin being integrated is installed. 34 | * 35 | * @return bool 36 | */ 37 | public static function isInstalled (): bool; 38 | 39 | } -------------------------------------------------------------------------------- /src/web/assets/OutIndex.min.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/assets/js/",n(n.s="./resources/js/OutIndex.js")}({"./resources/js/OutIndex.js":function(e,t){Craft.Out=Craft.Out||{},Craft.Out.ExportIndex=Craft.BaseElementIndex.extend({onUpdateElements:function(){this.base();const e=this.$elements[0].querySelectorAll("a[data-out-dl]");for(let t=0,n=e.length;t 2 | 5 | 6 | Plugin Icon 7 | Created with Sketch. 8 | 9 | 11 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/migrations/Install.php: -------------------------------------------------------------------------------- 1 | db->tableExists('{{%out_exports}}')) 27 | { 28 | // create the exports table 29 | $this->createTable('{{%out_exports}}', [ 30 | 'id' => $this->integer()->notNull(), 31 | 'title' => $this->string()->notNull(), 32 | 33 | 'elementType' => $this->string()->notNull(), 34 | 'elementSource' => $this->string()->notNull(), 35 | 36 | 'order' => $this->string()->null(), 37 | 'search' => $this->string()->null(), 38 | 'limit' => $this->integer()->null(), 39 | 'startDate' => $this->dateTime()->null(), 40 | 'endDate' => $this->dateTime()->null(), 41 | 42 | 'fieldSettings' => $this->json()->notNull(), 43 | 44 | 'dateCreated' => $this->dateTime()->notNull(), 45 | 'dateUpdated' => $this->dateTime()->notNull(), 46 | 'uid' => $this->uid(), 47 | 'PRIMARY KEY(id)', 48 | ]); 49 | 50 | // give it a FK to the elements table 51 | $this->addForeignKey( 52 | $this->db->getForeignKeyName('{{%out_exports}}', 'id'), 53 | '{{%out_exports}}', 'id', '{{%elements}}', 'id', 'CASCADE', null 54 | ); 55 | } 56 | 57 | } 58 | 59 | public function safeDown () 60 | { 61 | $this->dropTableIfExists('{{%out_exports}}'); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/icon-mask.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Sidebar Icon 7 | Created with Sketch. 8 | 9 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/integrations/SproutFormsIntegration.php: -------------------------------------------------------------------------------- 1 | types[] = Form::class; 23 | $event->types[] = Entry::class; 24 | } 25 | ); 26 | } 27 | 28 | public static function fields (): array 29 | { 30 | $fields = []; 31 | 32 | $fields[Form::class] = function (Form $element) { 33 | return array_reduce( 34 | $element->getFields(), 35 | function ($carry, Field $field) { 36 | $carry[$field->handle] = [ 37 | 'name' => $field->name, 38 | 'handle' => $field->handle, 39 | 'type' => get_class($field), 40 | 'twig' => "{{ element.{$field->handle} }}", 41 | ]; 42 | return $carry; 43 | }, 44 | [] 45 | ); 46 | }; 47 | 48 | $fields[Entry::class] = function (Entry $element) { 49 | return array_reduce( 50 | $element->getFields(), 51 | function ($carry, Field $field) { 52 | $carry[$field->handle] = [ 53 | 'name' => $field->name, 54 | 'handle' => $field->handle, 55 | 'type' => get_class($field), 56 | 'twig' => "{{ element.{$field->handle} }}", 57 | ]; 58 | 59 | return $carry; 60 | }, 61 | [] 62 | ); 63 | }; 64 | 65 | return $fields; 66 | } 67 | 68 | public static function isInstalled (): bool 69 | { 70 | return \Craft::$app->plugins->isPluginInstalled('sprout-forms'); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/Out.php: -------------------------------------------------------------------------------- 1 | setComponents([ 58 | 'out' => OutService::class, 59 | ]); 60 | 61 | // Events 62 | // --------------------------------------------------------------------- 63 | 64 | Event::on( 65 | UrlManager::class, 66 | UrlManager::EVENT_REGISTER_CP_URL_RULES, 67 | [$this, 'onRegisterCpUrlRules'] 68 | ); 69 | 70 | Event::on( 71 | UserPermissions::class, 72 | UserPermissions::EVENT_REGISTER_PERMISSIONS, 73 | [$this, 'onRegisterUserPermissions'] 74 | ); 75 | 76 | Event::on( 77 | Elements::class, 78 | Elements::EVENT_REGISTER_ELEMENT_TYPES, 79 | [$this, 'onRegisterElementTypes'] 80 | ); 81 | 82 | // Integrations 83 | // --------------------------------------------------------------------- 84 | 85 | $request = Craft::$app->request; 86 | if ($request->isCpRequest && strpos($request->url, 'out') !== false) 87 | Integrations::register(); 88 | 89 | } 90 | 91 | // Events 92 | // ========================================================================= 93 | 94 | public function onRegisterCpUrlRules (RegisterUrlRulesEvent $event) 95 | { 96 | $user = Craft::$app->user; 97 | 98 | if ($user->checkPermission('accessOut') || $user->getIsAdmin()) 99 | $event->rules['out'] = 'out/out/index'; 100 | 101 | if ($user->checkPermission('out_createExport') || $user->getIsAdmin()) 102 | { 103 | $event->rules['out/new'] = 'out/out/edit'; 104 | $event->rules['out/'] = 'out/out/edit'; 105 | } 106 | 107 | if ( 108 | $user->checkPermission('out_createExport') 109 | || $user->checkPermission('out_downloadExport') 110 | || $user->getIsAdmin() 111 | ) { 112 | $event->rules['out/dl/'] = 'out/out/dl'; 113 | } 114 | } 115 | 116 | public function onRegisterUserPermissions (RegisterUserPermissionsEvent $event) 117 | { 118 | $event->permissions['Out'] = [ 119 | 'accessOut' => [ 120 | 'label' => 'Access Out', 121 | ], 122 | 'out_createExport' => [ 123 | 'label' => 'Create Exports', 124 | ], 125 | 'out_downloadExport' => [ 126 | 'label' => 'Download Exports', 127 | ], 128 | ]; 129 | } 130 | 131 | public function onRegisterElementTypes (RegisterComponentTypesEvent $event) 132 | { 133 | $event->types[] = Export::class; 134 | } 135 | 136 | // Craft 137 | // ========================================================================= 138 | 139 | public function getCpNavItem () 140 | { 141 | $user = Craft::$app->user; 142 | if (!($user->checkPermission('accessOut') || $user->getIsAdmin())) 143 | return null; 144 | 145 | $item = parent::getCpNavItem(); 146 | 147 | $item['label'] = $this->getSettings()->pluginName; 148 | 149 | return $item; 150 | } 151 | 152 | // Settings 153 | // ------------------------------------------------------------------------- 154 | 155 | protected function createSettingsModel () 156 | { 157 | return new Settings(); 158 | } 159 | 160 | /** 161 | * @return bool|Model|null|Settings 162 | */ 163 | public function getSettings () 164 | { 165 | return parent::getSettings(); 166 | } 167 | 168 | /** 169 | * @return null|string 170 | * @throws Exception 171 | * @throws LoaderError 172 | * @throws RuntimeError 173 | * @throws SyntaxError 174 | */ 175 | protected function settingsHtml () 176 | { 177 | return Craft::$app->getView()->renderTemplate( 178 | 'out/settings', [ 179 | 'settings' => $this->getSettings() 180 | ]); 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /src/web/assets/OutIndex.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./resources/js/OutIndex.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","Craft","Out","ExportIndex","BaseElementIndex","extend","onUpdateElements","this","base","dls","$elements","querySelectorAll","length","Garnish","MenuBtn","$","registerElementIndexClass"],"mappings":"aACE,IAAIA,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUC,QAGnC,IAAIC,EAASJ,EAAiBE,GAAY,CACzCG,EAAGH,EACHI,GAAG,EACHH,QAAS,IAUV,OANAI,EAAQL,GAAUM,KAAKJ,EAAOD,QAASC,EAAQA,EAAOD,QAASF,GAG/DG,EAAOE,GAAI,EAGJF,EAAOD,QAKfF,EAAoBQ,EAAIF,EAGxBN,EAAoBS,EAAIV,EAGxBC,EAAoBU,EAAI,SAASR,EAASS,EAAMC,GAC3CZ,EAAoBa,EAAEX,EAASS,IAClCG,OAAOC,eAAeb,EAASS,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEZ,EAAoBkB,EAAI,SAAShB,GACX,oBAAXiB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAeb,EAASiB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,KAQvDrB,EAAoBsB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQrB,EAAoBqB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFA1B,EAAoBkB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOrB,EAAoBU,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRzB,EAAoB6B,EAAI,SAAS1B,GAChC,IAAIS,EAAST,GAAUA,EAAOqB,WAC7B,WAAwB,OAAOrB,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAH,EAAoBU,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRZ,EAAoBa,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG/B,EAAoBkC,EAAI,cAIjBlC,EAAoBA,EAAoBmC,EAAI,8B,6CChFrDC,MAAMC,IAAMD,MAAMC,KAAO,GAEzBD,MAAMC,IAAIC,YAAcF,MAAMG,iBAAiBC,OAAO,CACrDC,iBAAkB,WACjBC,KAAKC,OAEL,MAAMC,EAAMF,KAAKG,UAAU,GAAGC,iBAAiB,kBAE/C,IAAK,IAAI1C,EAAI,EAAGC,EAAIuC,EAAIG,OAAQ3C,EAAIC,IAAKD,EACxC,IAAI4C,QAAQC,QAAQC,EAAEN,EAAIxC,QAI7BgC,MAAMe,0BACL,+BACAf,MAAMC,IAAIC","file":"OutIndex.min.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/assets/js/\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./resources/js/OutIndex.js\");\n","/* global Craft, Garnish, $ */\n\nCraft.Out = Craft.Out || {};\n\nCraft.Out.ExportIndex = Craft.BaseElementIndex.extend({\n\tonUpdateElements: function () {\n\t\tthis.base();\n\n\t\tconst dls = this.$elements[0].querySelectorAll('a[data-out-dl]');\n\n\t\tfor (let i = 0, l = dls.length; i < l; ++i)\n\t\t\tnew Garnish.MenuBtn($(dls[i]));\n\t},\n});\n\nCraft.registerElementIndexClass(\n\t\"ether\\\\out\\\\elements\\\\Export\",\n\tCraft.Out.ExportIndex\n);"],"sourceRoot":""} -------------------------------------------------------------------------------- /src/templates/_edit.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% import "_includes/forms" as forms %} 3 | 4 | {% set title = export.id ? export.title : 'New Export' %} 5 | {% set fullPageForm = true %} 6 | {% set saveShortcutRedirect = continueEditingUrl %} 7 | 8 | {% block header %} 9 | {{ block('pageTitle') }} 10 |
11 | {{ block('actionButton') }} 12 | {% endblock %} 13 | 14 | {% block actionButton %} 15 |
16 | 17 | 18 | 19 | 59 |
60 | {% endblock %} 61 | 62 | {% js %} 63 | new Out({{ fields|json_encode|raw }}); 64 | {% endjs %} 65 | 66 | {% block content %} 67 | 68 | {{ redirectInput('out') }} 69 | 70 | {% if export.id %} 71 | 72 | 73 | {% endif %} 74 | 75 |
76 | {{ forms.textField({ 77 | label: "Title"|t('app'), 78 | id: 'title', 79 | name: 'title', 80 | value: export.title, 81 | first: true, 82 | autofocus: true, 83 | required: true, 84 | maxlength: 255, 85 | errors: export.getErrors('title'), 86 | }) }} 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | {% for col in export.fieldSettings %} 101 | 102 | 108 | 113 | 116 | 117 | 118 | 119 | {% endfor %} 120 | 121 |
Column HeadingType
103 | 104 | 105 | 106 | 107 | 109 | 112 | 114 | {{ col.type }} 115 |
122 |
Add Column
123 |
124 | 125 | {% endblock %} 126 | 127 | {% block details %} 128 |
129 | {% if not isNewExport %} 130 |
131 |

132 | Changing Element Type or Element Source 133 | may cause some columns to no longer work. 134 |

135 |
136 | {% endif %} 137 | 138 | {{ forms.selectField({ 139 | label: 'Element Type'|t('app'), 140 | id: 'elementType', 141 | name: 'elementType', 142 | options: elementTypes, 143 | value: export.elementType, 144 | }) }} 145 | 146 | {% for type, sources in elementSources %} 147 | {% set config = { 148 | label: 'Element Source', 149 | id: 'elementSource-' ~ type|replace('\\', '_'), 150 | name: 'elementSource', 151 | options: sources, 152 | value: export.elementSource, 153 | } %} 154 | {% embed '_includes/forms/field' with config|merge({ 155 | input: forms.select(config) 156 | }) %} 157 | {% block attr -%} 158 | data-source-type="{{ type }}" 159 | {%- if export.elementType != type or (not export.elementType and not loop.first) -%} 160 | {##} style="display:none" 161 | {%- endif -%} 162 | {%- endblock %} 163 | {% endembed %} 164 | {% endfor %} 165 | 166 | {{ forms.textField({ 167 | label: 'Order', 168 | id: 'order', 169 | name: 'order', 170 | value: export.order, 171 | }) }} 172 | 173 | {{ forms.textField({ 174 | label: 'Search Query', 175 | id: 'search', 176 | name: 'search', 177 | value: export.search, 178 | }) }} 179 | 180 | {{ forms.textField({ 181 | label: 'Limit', 182 | id: 'limit', 183 | name: 'limit', 184 | type: 'number', 185 | min: 1, 186 | placeholder: 'Unlimited', 187 | value: export.limit, 188 | }) }} 189 | 190 | {{ forms.dateTimeField({ 191 | label: 'Before', 192 | id: 'startDate', 193 | name: 'startDate', 194 | value: export.startDate, 195 | }) }} 196 | 197 | {{ forms.dateTimeField({ 198 | label: 'After', 199 | id: 'endDate', 200 | name: 'endDate', 201 | value: export.endDate, 202 | }) }} 203 |
204 | 205 | {% if export.id %} 206 |
207 |
208 |
{{ "Date Created"|t('app') }}
209 |
{{ export.dateCreated|datetime('short') }}
210 |
211 |
212 |
{{ "Date Updated"|t('app') }}
213 |
{{ export.dateUpdated|datetime('short') }}
214 |
215 |
216 | {% endif %} 217 | {% endblock %} -------------------------------------------------------------------------------- /src/services/OutService.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'name' => 'Title', 25 | 'handle' => 'title', 26 | 'type' => 'craft\fields\PlainText', 27 | ], 28 | 'dateCreated' => [ 29 | 'name' => 'Date Created', 30 | 'handle' => 'dateCreated', 31 | 'type' => 'craft\fields\Date', 32 | ], 33 | 'dateUpdated' => [ 34 | 'name' => 'Date Updated', 35 | 'handle' => 'dateUpdated', 36 | 'type' => 'craft\fields\Date', 37 | ], 38 | ]; 39 | 40 | /** @var Field $field */ 41 | foreach (Craft::$app->fields->getAllFields() as $field) 42 | $fields[$field->handle] = [ 43 | 'name' => $field->name, 44 | 'handle' => $field->handle, 45 | 'type' => get_class($field), 46 | ]; 47 | 48 | return $fields; 49 | } 50 | 51 | public function fieldsFromElementAndSource ($element, string $source) 52 | { 53 | $integrations = Integrations::fields(); 54 | 55 | if (!array_key_exists($element, $integrations)) 56 | return $this->fields(); 57 | 58 | /** @var Element $el */ 59 | $el = new $element; 60 | 61 | $criteria = null; 62 | 63 | foreach ($el->sources() as $src) 64 | if (array_key_exists('key', $src) && $src['key'] === $source) 65 | $criteria = $src['criteria']; 66 | 67 | if (!$criteria) 68 | return []; 69 | 70 | $query = $el::find(); 71 | 72 | Craft::configure($query, $criteria); 73 | 74 | $firstElement = $query->one(); 75 | 76 | if (!$firstElement) 77 | return []; 78 | 79 | return $integrations[$element]($firstElement); 80 | } 81 | 82 | /** 83 | * @param Export $export 84 | * @param int $siteId 85 | * 86 | * @throws ExitException 87 | */ 88 | public function generate (Export $export, int $siteId) 89 | { 90 | /** @var Element $element */ 91 | $element = new $export->elementType; 92 | 93 | // Build the criteria 94 | $criteria = []; 95 | 96 | foreach ($element->sources() as $source) 97 | { 98 | if ( 99 | array_key_exists('key', $source) 100 | && $source['key'] === $export->elementSource 101 | ) { 102 | $criteria = $source['criteria']; 103 | break; 104 | } 105 | } 106 | 107 | if (!empty($export->order)) 108 | $criteria['orderBy'] = $export->order; 109 | 110 | if (!empty($export->search)) 111 | $criteria['search'] = $export->search; 112 | 113 | if (!empty($export->limit)) 114 | $criteria['limit'] = $export->limit; 115 | 116 | if (!empty($export->startDate)) 117 | $criteria['after'] = $export->startDate; 118 | 119 | if (!empty($export->endDate)) 120 | $criteria['before'] = $export->endDate; 121 | 122 | /** @var ElementQuery $query */ 123 | $query = $element::find()->siteId($siteId); 124 | 125 | Craft::configure($query, $criteria); 126 | 127 | $split = Out::getInstance()->getSettings()['split']; 128 | 129 | if ($query->count() > $split) 130 | $this->_renderMultiple($export, $query, $split); 131 | else 132 | $this->_renderSingle($export, $query); 133 | } 134 | 135 | /** 136 | * @param $export 137 | * @param ElementQuery $query 138 | * 139 | * @throws ExitException 140 | */ 141 | private function _renderSingle ($export, ElementQuery $query) 142 | { 143 | $filename = StringHelper::toKebabCase($export->title); 144 | 145 | header('Content-Type: application/csv'); 146 | header('Content-Disposition: attachment; filename="' . $filename . '.csv"'); 147 | header('Pragma: no-cache'); 148 | 149 | echo $this->_renderCsv($export, $query); 150 | 151 | exit(200); 152 | } 153 | 154 | private function _renderMultiple ($export, ElementQuery $query, $split) 155 | { 156 | $count = $query->count(); 157 | $pages = ceil($count / $split); 158 | 159 | $filename = StringHelper::toKebabCase($export->title); 160 | 161 | 162 | $file = @tempnam('tmp', 'zip'); 163 | $zip = new ZipArchive(); 164 | $zip->open($file, ZipArchive::CREATE); 165 | 166 | while ($pages--) 167 | { 168 | $zip->addFromString( 169 | $filename . '.' . ($pages + 1) . '.csv', 170 | $this->_renderCsv($export, clone $query, $split * $pages, $split) 171 | ); 172 | } 173 | 174 | $zip->close(); 175 | 176 | header('Content-Type: application/zip'); 177 | header('Content-Length: ' . filesize($file)); 178 | header('Content-Disposition: attachment; filename="' . $filename . '.zip"'); 179 | readfile($file); 180 | unlink($file); 181 | } 182 | 183 | private function _renderCsv ($export, ElementQuery $query, $offset = 0, $limit = null) 184 | { 185 | ob_start(); 186 | 187 | $out = fopen('php://output', 'w'); 188 | 189 | // Output header 190 | fputcsv($out, $this->_header($export)); 191 | 192 | // Output elements 193 | /** @var Element $item */ 194 | foreach ($query->limit($limit)->offset($offset)->all() as $item) 195 | fputcsv($out, $this->_row($export, $item)); 196 | 197 | // End CSV output 198 | fclose($out); 199 | 200 | $out = ob_get_clean(); 201 | $out = str_replace("\n", "\r\n", $out); 202 | 203 | return $out; 204 | } 205 | 206 | private function _header (Export $export) 207 | { 208 | $fieldSettings = $export->fieldSettings; 209 | 210 | $header = []; 211 | 212 | foreach ($fieldSettings as $field) 213 | { 214 | $name = $field['name']; 215 | $split = $field['split'] === '1'; 216 | 217 | // TODO: Split non-escaped commas only (, not ",") 218 | if ($split) $header = array_merge($header, explode(',', $name)); 219 | else $header[] = $name; 220 | } 221 | 222 | return $header; 223 | } 224 | 225 | private function _row (Export $export, Element $element) 226 | { 227 | $fieldSettings = $export->fieldSettings; 228 | 229 | $row = []; 230 | 231 | foreach ($fieldSettings as $field) 232 | { 233 | $twig = $field['twig']; 234 | $split = $field['split'] === '1'; 235 | 236 | $value = Craft::$app->view->renderString( 237 | $twig, 238 | compact('element') 239 | ); 240 | 241 | // TODO: Split non-escaped commas only (, not ",") 242 | if ($split) $row = array_merge($row, explode(',', $value)); 243 | else $row[] = $value; 244 | } 245 | 246 | return $row; 247 | } 248 | 249 | } -------------------------------------------------------------------------------- /src/controllers/OutController.php: -------------------------------------------------------------------------------- 1 | view->registerAssetBundle(OutIndexAsset::class); 48 | 49 | return $this->renderTemplate('out/index', [ 50 | 'pluginName' => Out::getInstance()->getSettings()->pluginName, 51 | 'exportName' => Out::getInstance()->getSettings()->exportName, 52 | ]); 53 | } 54 | 55 | /** 56 | * @param string|null $exportId 57 | * 58 | * @return Response 59 | * @throws HttpException 60 | * @throws InvalidConfigException 61 | */ 62 | public function actionEdit ($exportId = null) 63 | { 64 | $craft = Craft::$app; 65 | 66 | $variables = [ 67 | 'continueEditingUrl' => 'out/{id}', 68 | 'nextExportUrl' => 'out/new', 69 | 'isNewExport' => $exportId === null, 70 | ]; 71 | 72 | // Export 73 | if ($exportId) 74 | { 75 | /** @var Export $export */ 76 | $export = Export::find()->id($exportId)->one(); 77 | if (!$export) throw new HttpException(404); 78 | $variables['export'] = $export; 79 | } else { 80 | $variables['export'] = new Export(); 81 | } 82 | 83 | // Breadcrumbs 84 | $variables['crumbs'] = [ 85 | [ 86 | 'label' => Out::getInstance()->getSettings()->pluginName, 87 | 'url' => UrlHelper::cpUrl('out'), 88 | ], 89 | ]; 90 | 91 | // Element Types 92 | $variables['elementSources'] = []; 93 | $variables['elementTypes'] = []; 94 | 95 | foreach ($craft->elements->getAllElementTypes() as $el) 96 | { 97 | if ($el === Export::class) 98 | continue; 99 | 100 | /** @var Element $type */ 101 | $type = new $el; 102 | 103 | $sources = []; 104 | 105 | foreach ($type->sources() as $source) 106 | { 107 | if ( 108 | !array_key_exists('key', $source) 109 | || !array_key_exists('label', $source) 110 | || $source['key'] === '*' 111 | ) continue; 112 | 113 | $sources[] = [ 114 | 'label' => $source['label'], 115 | 'value' => $source['key'], 116 | ]; 117 | } 118 | 119 | if (empty($sources)) 120 | continue; 121 | 122 | $variables['elementSources'][$el] = $sources; 123 | 124 | $variables['elementTypes'][] = [ 125 | 'label' => $type->displayName() ?: $el, 126 | 'value' => $el, 127 | ]; 128 | } 129 | 130 | // Fields 131 | if ($exportId) 132 | { 133 | $variables['fields'] = Out::getInstance()->out->fieldsFromElementAndSource( 134 | $variables['export']->elementType, 135 | $variables['export']->elementSource 136 | ); 137 | } 138 | else 139 | { 140 | $variables['fields'] = Out::getInstance()->out->fields(); 141 | } 142 | 143 | // Integrations 144 | $variables['integrations'] = array_keys(Integrations::fields()); 145 | 146 | // Asset 147 | $craft->view->registerAssetBundle(OutAsset::class); 148 | 149 | return $this->renderTemplate( 150 | 'out/_edit', 151 | $variables 152 | ); 153 | } 154 | 155 | /** 156 | * @throws Throwable 157 | * @throws ElementNotFoundException 158 | * @throws Exception 159 | * @throws BadRequestHttpException 160 | */ 161 | public function actionSave () 162 | { 163 | $request = Craft::$app->request; 164 | 165 | $export = new Export(); 166 | $export->id = $request->getParam('exportId'); 167 | $export->uid = $request->getParam('exportUid'); 168 | $export->title = $request->getRequiredParam('title'); 169 | $export->elementType = $request->getRequiredParam('elementType'); 170 | $export->elementSource = $request->getRequiredParam('elementSource'); 171 | $export->order = $request->getParam('order'); 172 | $export->search = $request->getParam('search'); 173 | $export->limit = $request->getParam('limit'); 174 | $export->startDate = DateTimeHelper::toDateTime($request->getParam('startDate')) ?: null; 175 | $export->endDate = DateTimeHelper::toDateTime($request->getParam('endDate')) ?: null; 176 | $export->fieldSettings = $request->getParam('fieldSettings'); 177 | 178 | if (Craft::$app->elements->saveElement($export)) 179 | return $this->redirectToPostedUrl($export); 180 | 181 | Craft::$app->getSession()->setError( 182 | Craft::t('out', 'Couldn’t save export.') 183 | ); 184 | 185 | Craft::$app->getUrlManager()->setRouteParams([ 186 | 'export' => $export 187 | ]); 188 | 189 | return null; 190 | } 191 | 192 | /** 193 | * @throws Throwable 194 | * @throws BadRequestHttpException 195 | */ 196 | public function actionDelete () 197 | { 198 | $exportId = Craft::$app->getRequest()->getRequiredBodyParam('exportId'); 199 | Craft::$app->elements->deleteElementById($exportId); 200 | 201 | return $this->redirect(UrlHelper::cpUrl('out')); 202 | } 203 | 204 | /** 205 | * @param $exportId 206 | * 207 | * @throws HttpException 208 | * @throws ExitException 209 | */ 210 | public function actionDl ($exportId) 211 | { 212 | /** @var Export $export */ 213 | $export = Export::find()->id($exportId)->one(); 214 | $siteId = Craft::$app->request->getParam( 215 | 'siteId', 216 | Craft::$app->sites->primarySite->id 217 | ); 218 | 219 | if (!$export) throw new HttpException(404); 220 | 221 | Out::getInstance()->out->generate($export, $siteId); 222 | } 223 | 224 | /** 225 | * @return Response 226 | * @throws BadRequestHttpException 227 | */ 228 | public function actionFields () 229 | { 230 | $request = Craft::$app->request; 231 | $element = $request->getRequiredParam('element'); 232 | $source = $request->getRequiredParam('source'); 233 | 234 | return $this->asJson(Out::getInstance()->out->fieldsFromElementAndSource( 235 | $element, 236 | $source 237 | )); 238 | } 239 | 240 | } -------------------------------------------------------------------------------- /src/elements/Export.php: -------------------------------------------------------------------------------- 1 | db->createCommand()->insert( 97 | '{{%out_exports}}', 98 | array_merge($this->_map(), ['id' => $this->id]) 99 | )->execute(); 100 | } 101 | else 102 | { 103 | Craft::$app->db->createCommand()->update( 104 | '{{%out_exports}}', 105 | $this->_map(), 106 | ['id' => $this->id] 107 | )->execute(); 108 | } 109 | 110 | return parent::afterSave($isNew); 111 | } 112 | 113 | public static function find (): ElementQueryInterface 114 | { 115 | return new ExportQuery(static::class); 116 | } 117 | 118 | public function getCpEditUrl (): string 119 | { 120 | if (self::_canCreate()) 121 | return UrlHelper::cpUrl('out/' . $this->id); 122 | 123 | return ''; 124 | } 125 | 126 | public function getFieldLayout () 127 | { 128 | if (!$this->fieldLayoutId) 129 | return new FieldLayout(); 130 | 131 | return parent::getFieldLayout(); 132 | } 133 | 134 | protected static function defineTableAttributes (): array 135 | { 136 | $attrs = [ 137 | 'title' => ['label' => Craft::t('app', 'Title')], 138 | 'dateCreated' => ['label' => Craft::t('app', 'Date Created')], 139 | 'dateUpdated' => ['label' => Craft::t('app', 'Date Updated')], 140 | ]; 141 | 142 | if (self::_canDownload()) 143 | $attrs['dl'] = ['label' => 'Download']; 144 | 145 | return $attrs; 146 | } 147 | 148 | protected static function defineDefaultTableAttributes (string $source): array 149 | { 150 | $attrs = []; 151 | 152 | $attrs[] = 'title'; 153 | $attrs[] = 'dateCreated'; 154 | $attrs[] = 'dateUpdated'; 155 | 156 | if (self::_canDownload()) 157 | $attrs[] = 'dl'; 158 | 159 | return $attrs; 160 | } 161 | 162 | protected function tableAttributeHtml (string $attribute): string 163 | { 164 | if ($attribute === 'dl' && self::_canDownload()) 165 | { 166 | $icon = ''; 167 | 168 | $dl = UrlHelper::cpUrl('out/dl/' . $this->id); 169 | 170 | $sites = Craft::$app->sites->getAllSites(); 171 | 172 | if (count($sites) === 1) 173 | return '' . $icon . ''; 174 | 175 | $actions = ''; 176 | 177 | foreach ($sites as $site) 178 | $actions .= '
  • ' . $site->name . '
  • '; 179 | 180 | return <<$icon 182 | 187 | HTML; 188 | } 189 | 190 | return parent::tableAttributeHtml($attribute); 191 | } 192 | 193 | public static function sortOptions (): array 194 | { 195 | return [ 196 | 'title' => Craft::t('app', 'Title'), 197 | 'dateCreated' => Craft::t('app', 'Date Created'), 198 | 'dateUpdated' => Craft::t('app', 'Date Updated'), 199 | ]; 200 | } 201 | 202 | public static function sources (string $context = null): array 203 | { 204 | $name = Inflector::pluralize(Out::getInstance()->getSettings()->exportName); 205 | 206 | return [ 207 | '*' => [ 208 | 'key' => '*', 209 | 'label' => 'All ' . $name, 210 | ], 211 | ]; 212 | } 213 | 214 | protected static function defineExporters (string $source): array 215 | { 216 | return []; 217 | } 218 | 219 | // Getters / Setters 220 | // ========================================================================= 221 | 222 | public function setFieldSettings ($value) 223 | { 224 | if (is_array($value)) 225 | return $this->_fieldSettings = $value; 226 | 227 | return $this->_fieldSettings = Json::decodeIfJson($value); 228 | } 229 | 230 | public function getFieldSettings (): array 231 | { 232 | if (is_array($this->_fieldSettings)) 233 | return $this->_fieldSettings; 234 | 235 | return $this->_fieldSettings = Json::decode($this->_fieldSettings ?: '{}'); 236 | } 237 | 238 | // Helpers 239 | // ========================================================================= 240 | 241 | private function _map () 242 | { 243 | return [ 244 | 'title' => $this->title, 245 | 'elementType' => $this->elementType, 246 | 'elementSource' => $this->elementSource, 247 | 'order' => $this->order, 248 | 'search' => $this->search, 249 | 'limit' => $this->limit, 250 | 'startDate' => $this->startDate, 251 | 'endDate' => $this->endDate, 252 | 'fieldSettings' => Json::encode($this->fieldSettings), 253 | ]; 254 | } 255 | 256 | private static function _canCreate () 257 | { 258 | return ( 259 | Craft::$app->user->checkPermission('out_createExport') 260 | || Craft::$app->user->getIsAdmin() 261 | ); 262 | } 263 | 264 | private static function _canDownload () 265 | { 266 | return ( 267 | Craft::$app->user->checkPermission('out_createExport') 268 | || Craft::$app->user->checkPermission('out_downloadExport') 269 | || Craft::$app->user->getIsAdmin() 270 | ); 271 | } 272 | 273 | } -------------------------------------------------------------------------------- /src/web/assets/OutEdit.min.js: -------------------------------------------------------------------------------- 1 | !function(e){var o={};function l(n){if(o[n])return o[n].exports;var s=o[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,l),s.l=!0,s.exports}l.m=e,l.c=o,l.d=function(e,o,n){l.o(e,o)||Object.defineProperty(e,o,{enumerable:!0,get:n})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,o){if(1&o&&(e=l(e)),8&o)return e;if(4&o&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(l.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&o&&"string"!=typeof e)for(var s in e)l.d(n,s,function(o){return e[o]}.bind(null,s));return n},l.n=function(e){var o=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(o,"a",o),o},l.o=function(e,o){return Object.prototype.hasOwnProperty.call(e,o)},l.p="/assets/js/",l(l.s="./resources/js/OutEdit.js")}({"../../../../../usr/local/lib/node_modules/build/node_modules/@babel/runtime/helpers/defineProperty.js":function(e,o){e.exports=function(e,o,l){return o in e?Object.defineProperty(e,o,{value:l,enumerable:!0,configurable:!0,writable:!0}):e[o]=l,e}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/a-function.js":function(e,o){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/a-possible-prototype.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-object.js");e.exports=function(e){if(!n(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/add-to-unscopables.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-create.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-define-property.js"),t=n("unscopables"),i=Array.prototype;null==i[t]&&r.f(i,t,{configurable:!0,value:s(null)}),e.exports=function(e){i[t][e]=!0}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/advance-string-index.js":function(e,o,l){"use strict";var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/string-multibyte.js").charAt;e.exports=function(e,o,l){return o+(l?n(e,o).length:1)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/an-instance.js":function(e,o){e.exports=function(e,o,l){if(!(e instanceof o))throw TypeError("Incorrect "+(l?l+" ":"")+"invocation");return e}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/an-object.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-object.js");e.exports=function(e){if(!n(e))throw TypeError(String(e)+" is not an object");return e}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/array-includes.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/to-indexed-object.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/to-length.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/to-absolute-index.js"),t=function(e){return function(o,l,t){var i,u=n(o),d=s(u.length),c=r(t,d);if(e&&l!=l){for(;d>c;)if((i=u[c++])!=i)return!0}else for(;d>c;c++)if((e||c in u)&&u[c]===l)return e||c||0;return!e&&-1}};e.exports={includes:t(!0),indexOf:t(!1)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/call-with-safe-iteration-closing.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/an-object.js");e.exports=function(e,o,l,s){try{return s?o(n(l)[0],l[1]):o(l)}catch(o){var r=e.return;throw void 0!==r&&n(r.call(e)),o}}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/check-correctness-of-iteration.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js")("iterator"),s=!1;try{var r=0,t={next:function(){return{done:!!r++}},return:function(){s=!0}};t[n]=function(){return this},Array.from(t,(function(){throw 2}))}catch(e){}e.exports=function(e,o){if(!o&&!s)return!1;var l=!1;try{var r={};r[n]=function(){return{next:function(){return{done:l=!0}}}},e(r)}catch(e){}return l}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof-raw.js":function(e,o){var l={}.toString;e.exports=function(e){return l.call(e).slice(8,-1)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/to-string-tag-support.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof-raw.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js")("toStringTag"),t="Arguments"==s(function(){return arguments}());e.exports=n?s:function(e){var o,l,n;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(l=function(e,o){try{return e[o]}catch(e){}}(o=Object(e),r))?l:t?s(o):"Object"==(n=s(o))&&"function"==typeof o.callee?"Arguments":n}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/copy-constructor-properties.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/has.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/own-keys.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-get-own-property-descriptor.js"),t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-define-property.js");e.exports=function(e,o){for(var l=s(o),i=t.f,u=r.f,d=0;d=74)&&(n=t.match(/Chrome\/(\d+)/))&&(s=n[1]),e.exports=s&&+s},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/enum-bug-keys.js":function(e,o){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/export.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-get-own-property-descriptor.js").f,r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/create-non-enumerable-property.js"),t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/redefine.js"),i=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/set-global.js"),u=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/copy-constructor-properties.js"),d=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-forced.js");e.exports=function(e,o){var l,c,a,b,m,j=e.target,_=e.global,f=e.stat;if(l=_?n:f?n[j]||i(j,{}):(n[j]||{}).prototype)for(c in o){if(b=o[c],a=e.noTargetGet?(m=s(l,c))&&m.value:l[c],!d(_?c:j+(f?".":"#")+c,e.forced)&&void 0!==a){if(typeof b==typeof a)continue;u(b,a)}(e.sham||a&&a.sham)&&r(b,"sham",!0),t(l,c,b,e)}}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fails.js":function(e,o){e.exports=function(e){try{return!!e()}catch(e){return!0}}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fix-regexp-well-known-symbol-logic.js":function(e,o,l){"use strict";l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/modules/es.regexp.exec.js");var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/redefine.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fails.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js"),t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/regexp-exec.js"),i=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/create-non-enumerable-property.js"),u=r("species"),d=!s((function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")})),c="$0"==="a".replace(/./,"$0"),a=r("replace"),b=!!/./[a]&&""===/./[a]("a","$0"),m=!s((function(){var e=/(?:)/,o=e.exec;e.exec=function(){return o.apply(this,arguments)};var l="ab".split(e);return 2!==l.length||"a"!==l[0]||"b"!==l[1]}));e.exports=function(e,o,l,a){var j=r(e),_=!s((function(){var o={};return o[j]=function(){return 7},7!=""[e](o)})),f=_&&!s((function(){var o=!1,l=/a/;return"split"===e&&((l={}).constructor={},l.constructor[u]=function(){return l},l.flags="",l[j]=/./[j]),l.exec=function(){return o=!0,null},l[j](""),!o}));if(!_||!f||"replace"===e&&(!d||!c||b)||"split"===e&&!m){var p=/./[j],h=l(j,""[e],(function(e,o,l,n,s){return o.exec===t?_&&!s?{done:!0,value:p.call(o,l,n)}:{done:!0,value:e.call(l,o,n)}:{done:!1}}),{REPLACE_KEEPS_$0:c,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:b}),v=h[0],g=h[1];n(String.prototype,e,v),n(RegExp.prototype,j,2==o?function(e,o){return g.call(e,this,o)}:function(e){return g.call(e,this)})}a&&i(RegExp.prototype[j],"sham",!0)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/function-bind-context.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/a-function.js");e.exports=function(e,o,l){if(n(e),void 0===o)return e;switch(l){case 0:return function(){return e.call(o)};case 1:return function(l){return e.call(o,l)};case 2:return function(l,n){return e.call(o,l,n)};case 3:return function(l,n,s){return e.call(o,l,n,s)}}return function(){return e.apply(o,arguments)}}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/get-built-in.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/path.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js"),r=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,o){return arguments.length<2?r(n[e])||r(s[e]):n[e]&&n[e][o]||s[e]&&s[e][o]}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/get-iterator-method.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/iterators.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js")("iterator");e.exports=function(e){if(null!=e)return e[r]||e["@@iterator"]||s[n(e)]}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js":function(e,o,l){(function(o){var l=function(e){return e&&e.Math==Math&&e};e.exports=l("object"==typeof globalThis&&globalThis)||l("object"==typeof window&&window)||l("object"==typeof self&&self)||l("object"==typeof o&&o)||Function("return this")()}).call(this,l("../../../../../usr/local/lib/node_modules/build/node_modules/webpack/buildin/global.js"))},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/has.js":function(e,o){var l={}.hasOwnProperty;e.exports=function(e,o){return l.call(e,o)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/hidden-keys.js":function(e,o){e.exports={}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/host-report-errors.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js");e.exports=function(e,o){var l=n.console;l&&l.error&&(1===arguments.length?l.error(e):l.error(e,o))}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/html.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/get-built-in.js");e.exports=n("document","documentElement")},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/ie8-dom-define.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/descriptors.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fails.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/document-create-element.js");e.exports=!n&&!s((function(){return 7!=Object.defineProperty(r("div"),"a",{get:function(){return 7}}).a}))},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/indexed-object.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fails.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof-raw.js"),r="".split;e.exports=n((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==s(e)?r.call(e,""):Object(e)}:Object},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/inspect-source.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/shared-store.js"),s=Function.toString;"function"!=typeof n.inspectSource&&(n.inspectSource=function(e){return s.call(e)}),e.exports=n.inspectSource},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/internal-state.js":function(e,o,l){var n,s,r,t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/native-weak-map.js"),i=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js"),u=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-object.js"),d=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/create-non-enumerable-property.js"),c=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/has.js"),a=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/shared-key.js"),b=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/hidden-keys.js"),m=i.WeakMap;if(t){var j=new m,_=j.get,f=j.has,p=j.set;n=function(e,o){return p.call(j,e,o),o},s=function(e){return _.call(j,e)||{}},r=function(e){return f.call(j,e)}}else{var h=a("state");b[h]=!0,n=function(e,o){return d(e,h,o),o},s=function(e){return c(e,h)?e[h]:{}},r=function(e){return c(e,h)}}e.exports={set:n,get:s,has:r,enforce:function(e){return r(e)?s(e):n(e,{})},getterFor:function(e){return function(o){var l;if(!u(o)||(l=s(o)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return l}}}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-array-iterator-method.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/iterators.js"),r=n("iterator"),t=Array.prototype;e.exports=function(e){return void 0!==e&&(s.Array===e||t[r]===e)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-forced.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fails.js"),s=/#|\.prototype\./,r=function(e,o){var l=i[t(e)];return l==d||l!=u&&("function"==typeof o?n(o):!!o)},t=r.normalize=function(e){return String(e).replace(s,".").toLowerCase()},i=r.data={},u=r.NATIVE="N",d=r.POLYFILL="P";e.exports=r},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-object.js":function(e,o){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-pure.js":function(e,o){e.exports=!1},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-regexp.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-object.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof-raw.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js")("match");e.exports=function(e){var o;return n(e)&&(void 0!==(o=e[r])?!!o:"RegExp"==s(e))}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/iterate.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/an-object.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-array-iterator-method.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/to-length.js"),t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/function-bind-context.js"),i=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/get-iterator-method.js"),u=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/call-with-safe-iteration-closing.js"),d=function(e,o){this.stopped=e,this.result=o};(e.exports=function(e,o,l,c,a){var b,m,j,_,f,p,h,v=t(o,l,c?2:1);if(a)b=e;else{if("function"!=typeof(m=i(e)))throw TypeError("Target is not iterable");if(s(m)){for(j=0,_=r(e.length);_>j;j++)if((f=c?v(n(h=e[j])[0],h[1]):v(e[j]))&&f instanceof d)return f;return new d(!1)}b=m.call(e)}for(p=b.next;!(h=p.call(b)).done;)if("object"==typeof(f=u(b,v,h.value,c))&&f&&f instanceof d)return f;return new d(!1)}).stop=function(e){return new d(!0,e)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/iterators-core.js":function(e,o,l){"use strict";var n,s,r,t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-get-prototype-of.js"),i=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/create-non-enumerable-property.js"),u=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/has.js"),d=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/well-known-symbol.js"),c=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/is-pure.js"),a=d("iterator"),b=!1;[].keys&&("next"in(r=[].keys())?(s=t(t(r)))!==Object.prototype&&(n=s):b=!0),null==n&&(n={}),c||u(n,a)||i(n,a,(function(){return this})),e.exports={IteratorPrototype:n,BUGGY_SAFARI_ITERATORS:b}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/iterators.js":function(e,o){e.exports={}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/microtask.js":function(e,o,l){var n,s,r,t,i,u,d,c,a=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js"),b=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-get-own-property-descriptor.js").f,m=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/classof-raw.js"),j=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/task.js").set,_=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/engine-is-ios.js"),f=a.MutationObserver||a.WebKitMutationObserver,p=a.process,h=a.Promise,v="process"==m(p),g=b(a,"queueMicrotask"),y=g&&g.value;y||(n=function(){var e,o;for(v&&(e=p.domain)&&e.exit();s;){o=s.fn,s=s.next;try{o()}catch(e){throw s?t():r=void 0,e}}r=void 0,e&&e.enter()},v?t=function(){p.nextTick(n)}:f&&!_?(i=!0,u=document.createTextNode(""),new f(n).observe(u,{characterData:!0}),t=function(){u.data=i=!i}):h&&h.resolve?(d=h.resolve(void 0),c=d.then,t=function(){c.call(d,n)}):t=function(){j.call(a,n)}),e.exports=y||function(e){var o={fn:e,next:void 0};r&&(r.next=o),s||(s=o,t()),r=o}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/native-promise-constructor.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js");e.exports=n.Promise},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/native-symbol.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/fails.js");e.exports=!!Object.getOwnPropertySymbols&&!n((function(){return!String(Symbol())}))},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/native-weak-map.js":function(e,o,l){var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/global.js"),s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/inspect-source.js"),r=n.WeakMap;e.exports="function"==typeof r&&/native code/.test(s(r))},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/new-promise-capability.js":function(e,o,l){"use strict";var n=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/a-function.js"),s=function(e){var o,l;this.promise=new e((function(e,n){if(void 0!==o||void 0!==l)throw TypeError("Bad Promise constructor");o=e,l=n})),this.resolve=n(o),this.reject=n(l)};e.exports.f=function(e){return new s(e)}},"../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-create.js":function(e,o,l){var n,s=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/an-object.js"),r=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/object-define-properties.js"),t=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/enum-bug-keys.js"),i=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/hidden-keys.js"),u=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/html.js"),d=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/document-create-element.js"),c=l("../../../../../usr/local/lib/node_modules/build/node_modules/core-js/internals/shared-key.js"),a=c("IE_PROTO"),b=function(){},m=function(e){return"