diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b364873..9a5a0312 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,13 +39,11 @@ bundle_app: script: # build all assets for views - npm run build - # download GeoLite2-Country and opawg/user-agents archives and extract them to writable/uploads - - wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/ - - wget -c "https://github.com/opawg/user-agents/archive/master.tar.gz" -O - | tar -xz -C ./writable/uploads/ + # download GeoLite2-City archive and extract it to writable/uploads + - wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/ # rename extracted archives' folders - - mv ./writable/uploads/GeoLite2-Country* ./writable/uploads/GeoLite2-Country - - mv ./writable/uploads/user-agents* ./writable/uploads/user-agents + - mv ./writable/uploads/GeoLite2-City* ./writable/uploads/GeoLite2-City # create bundle folder: uses .rsync-filter (-F) file to copy only needed files - rsync -avF --progress . ./bundle diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index df0c8a6e..19486297 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -12,16 +12,20 @@ PHP Dependencies: - [commonmark](https://commonmark.thephpleague.com/) ([BSD 3-Clause "New" or "Revised" License](https://github.com/thephpleague/commonmark/blob/latest/LICENSE)) - [phpdotenv](https://github.com/vlucas/phpdotenv) ([ BSD-3-Clause License ](https://github.com/vlucas/phpdotenv/blob/master/LICENSE)) - [HTML To Markdown for PHP](https://github.com/thephpleague/html-to-markdown) ([MIT License](https://github.com/thephpleague/html-to-markdown/blob/master/LICENSE)) +- [podlibre/user-agents-php](https://github.com/podlibre/user-agents-php) ([MIT License](https://github.com/podlibre/user-agents-php/blob/main/LICENSE)) +- [podlibre/ipcat](https://github.com/podlibre/ipcat) ([GNU General Public License v3.0](https://github.com/podlibre/ipcat/blob/master/LICENSE)) Javascript dependencies: - [rollup](https://rollupjs.org/) ([MIT License](https://github.com/rollup/rollup/blob/master/LICENSE.md)) - [tailwindcss](https://tailwindcss.com/) ([MIT License](https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE)) - [ProseMirror](https://prosemirror.net/) ([MIT License](https://github.com/ProseMirror/prosemirror/blob/master/LICENSE)) -- [D3: Data-Driven Documents](https://d3js.org) ([BSD 3-Clause "New" or "Revised" License](https://github.com/d3/d3/blob/master/LICENSE)) +- [amCharts 4](https://github.com/amcharts/amcharts4) ([Free amCharts license](https://github.com/amcharts/amcharts4/blob/master/dist/script/LICENSE)) - [Choices.js](https://joshuajohnson.co.uk/Choices/) ([MIT License](https://github.com/jshjohnson/Choices/blob/master/LICENSE)) Other: - [RemixIcon](https://remixicon.com/) ([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License)) -- [User agent list](https://github.com/opawg/user-agents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) +- [OPAWG/User agent list](https://github.com/opawg/user-agents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) +- [client9/ipcat](https://github.com/client9/ipcat) ([GNU General Public License v3.0](https://github.com/client9/ipcat/blob/master/LICENSE)) +- [GeoLite2 City](https://dev.maxmind.com/geoip/geoip2/geolite2/) ([Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://www.maxmind.com/en/geolite2/eula)) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 3a786da0..5d2c45f8 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -53,10 +53,14 @@ $routes->group(config('App')->installGateway, function ($routes) { ]); }); -// Route for podcast audio file analytics (/stats/podcast_id/episode_id/podcast_folder/filename.mp3) -$routes->add('stats/(:num)/(:num)/(:any)', 'Analytics::hit/$1/$2/$3', [ - 'as' => 'analytics_hit', -]); +// Route for podcast audio file analytics (/audio/podcast_id/episode_id/bytes_threshold/filesize/podcast_folder/filename.mp3) +$routes->add( + 'audio/(:num)/(:num)/(:num)/(:num)/(:any)', + 'Analytics::hit/$1/$2/$3/$4/$5', + [ + 'as' => 'analytics_hit', + ] +); // Show the Unknown UserAgents $routes->get('.well-known/unknown-useragents', 'UnknownUserAgents'); @@ -113,6 +117,26 @@ $routes->group( 'as' => 'podcast-delete', 'filter' => 'permission:podcasts-delete', ]); + $routes->get('analytics', 'Podcast::analytics/$1', [ + 'as' => 'podcast-analytics', + 'filter' => 'permission:podcasts-view,podcast-view', + ]); + $routes->get( + 'analytics-data/(:segment)/(:segment)', + 'AnalyticsData::getData/$1/$2/$3', + [ + 'as' => 'analytics-data', + 'filter' => 'permission:podcasts-view,podcast-view', + ] + ); + $routes->get( + 'analytics-data/(:segment)/(:segment)/(:num)', + 'AnalyticsData::getData/$1/$2/$3/$4', + [ + 'as' => 'analytics-filtered-data', + 'filter' => 'permission:podcasts-view,podcast-view', + ] + ); // Podcast episodes $routes->group('episodes', function ($routes) { diff --git a/app/Controllers/Admin/AnalyticsData.php b/app/Controllers/Admin/AnalyticsData.php new file mode 100644 index 00000000..a57960df --- /dev/null +++ b/app/Controllers/Admin/AnalyticsData.php @@ -0,0 +1,69 @@ + 2) { + if (!($this->podcast = (new PodcastModel())->find($params[0]))) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound( + 'Podcast not found: ' . $params[0] + ); + } + $this->className = '\App\Models\Analytics' . $params[1] . 'Model'; + $this->methodName = 'getData' . $params[2]; + if (count($params) > 3) { + if ( + !($this->episode = (new EpisodeModel()) + ->where([ + 'podcast_id' => $this->podcast->id, + 'id' => $params[3], + ]) + ->first()) + ) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound( + 'Episode not found: ' . $params[3] + ); + } + } + } + + return $this->$method(); + } + public function getData() + { + $analytics_model = new $this->className(); + $methodName = $this->methodName; + if ($this->episode) { + return $this->response->setJSON( + $analytics_model->$methodName( + $this->podcast->id, + $this->episode->id + ) + ); + } else { + return $this->response->setJSON( + $analytics_model->$methodName($this->podcast->id) + ); + } + } +} diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index 439a86da..64594e07 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -58,6 +58,14 @@ class Podcast extends BaseController return view('admin/podcast/view', $data); } + public function analytics() + { + $data = ['podcast' => $this->podcast]; + + replace_breadcrumb_params([0 => $this->podcast->title]); + return view('admin/podcast/analytics', $data); + } + public function create() { helper(['form', 'misc']); @@ -204,7 +212,9 @@ class Podcast extends BaseController $podcast = new \App\Entities\Podcast([ 'name' => $this->request->getPost('name'), 'imported_feed_url' => $this->request->getPost('imported_feed_url'), - + 'new_feed_url' => base_url( + route_to('podcast_feed', $this->request->getPost('name')) + ), 'title' => $feed->channel[0]->title, 'description' => $feed->channel[0]->description, 'image' => download_file($nsItunes->image->attributes()), @@ -214,7 +224,9 @@ class Podcast extends BaseController ? null : (in_array($nsItunes->explicit, ['yes', 'true']) ? 'explicit' - : null), + : (in_array($nsItunes->explicit, ['no', 'false']) + ? 'clean' + : null)), 'owner_name' => $nsItunes->owner->name, 'owner_email' => $nsItunes->owner->email, 'publisher' => $nsItunes->author, @@ -302,11 +314,13 @@ class Podcast extends BaseController 'image' => empty($nsItunes->image->attributes()) ? null : download_file($nsItunes->image->attributes()), - 'explicit' => $nsItunes->explicit - ? (in_array($nsItunes->explicit, ['yes', 'true']) + 'parental_advisory' => empty($nsItunes->explicit) + ? null + : (in_array($nsItunes->explicit, ['yes', 'true']) ? 'explicit' - : null) - : null, + : (in_array($nsItunes->explicit, ['no', 'false']) + ? 'clean' + : null)), 'number' => $this->request->getPost('force_renumber') === 'yes' ? $itemNumber diff --git a/app/Controllers/Analytics.php b/app/Controllers/Analytics.php index ec89dc22..3482b1eb 100644 --- a/app/Controllers/Analytics.php +++ b/app/Controllers/Analytics.php @@ -40,16 +40,22 @@ class Analytics extends Controller // E.g.: // $this->session = \Config\Services::session(); - set_user_session_country(); + set_user_session_deny_list_ip(); + set_user_session_location(); set_user_session_player(); } // Add one hit to this episode: - public function hit($p_podcastId, $p_episodeId, ...$filename) - { + public function hit( + $podcastId, + $episodeId, + $bytesThreshold, + $fileSize, + ...$filename + ) { helper('media'); - podcast_hit($p_podcastId, $p_episodeId); + podcast_hit($podcastId, $episodeId, $bytesThreshold, $fileSize); return redirect()->to(media_url(implode('/', $filename))); } } diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php index 2f5bdcff..3bfbd4b0 100644 --- a/app/Controllers/BaseController.php +++ b/app/Controllers/BaseController.php @@ -45,9 +45,10 @@ class BaseController extends Controller // E.g.: // $this->session = \Config\Services::session(); - set_user_session_country(); + set_user_session_deny_list_ip(); set_user_session_browser(); set_user_session_referer(); + set_user_session_entry_page(); } protected static function triggerWebpageHit($podcastId) diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php index a95e4db1..018315cf 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -110,6 +110,13 @@ class AddPodcasts extends Migration 'The RSS feed URL if this podcast was imported, NULL otherwise.', 'null' => true, ], + 'new_feed_url' => [ + 'type' => 'VARCHAR', + 'constraint' => 1024, + 'comment' => + 'The RSS new feed URL if this podcast is moving out, NULL otherwise.', + 'null' => true, + ], 'created_at' => [ 'type' => 'TIMESTAMP', ], diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php index 24b2f02f..31334686 100644 --- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php +++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php @@ -61,6 +61,12 @@ class AddEpisodes extends Migration 'unsigned' => true, 'comment' => 'File size in bytes', ], + 'enclosure_headersize' => [ + 'type' => 'INT', + 'constraint' => 10, + 'unsigned' => true, + 'comment' => 'Header size in bytes', + ], 'description' => [ 'type' => 'TEXT', 'null' => true, diff --git a/app/Database/Migrations/2020-06-08-120000_add_analytics_podcasts.php b/app/Database/Migrations/2020-06-08-120000_add_analytics_podcasts.php new file mode 100644 index 00000000..e31d932d --- /dev/null +++ b/app/Database/Migrations/2020-06-08-120000_add_analytics_podcasts.php @@ -0,0 +1,49 @@ +forge->addField([ + 'podcast_id' => [ + 'type' => 'BIGINT', + 'constraint' => 20, + 'unsigned' => true, + ], + 'date' => [ + 'type' => 'date', + ], + 'hits' => [ + 'type' => 'INT', + 'constraint' => 10, + 'default' => 1, + ], + ]); + $this->forge->addPrimaryKey(['podcast_id', 'date']); + $this->forge->addField( + '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' + ); + $this->forge->addField( + '`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()' + ); + $this->forge->addForeignKey('podcast_id', 'podcasts', 'id'); + $this->forge->createTable('analytics_podcasts'); + } + + public function down() + { + $this->forge->dropTable('analytics_podcasts'); + } +} diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php b/app/Database/Migrations/2020-06-08-130000_add_analytics_podcasts_by_episode.php similarity index 67% rename from app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php rename to app/Database/Migrations/2020-06-08-130000_add_analytics_podcasts_by_episode.php index d0e94c6c..b189ef06 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_country.php +++ b/app/Database/Migrations/2020-06-08-130000_add_analytics_podcasts_by_episode.php @@ -12,34 +12,28 @@ namespace App\Database\Migrations; use CodeIgniter\Database\Migration; -class AddAnalyticsEpisodesByCountry extends Migration +class AddAnalyticsPodcastsByEpisode extends Migration { public function up() { $this->forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], 'podcast_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], + 'date' => [ + 'type' => 'date', + ], 'episode_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], - 'country_code' => [ - 'type' => 'VARCHAR', - 'constraint' => 3, - 'comment' => 'ISO 3166-1 code.', - ], - 'date' => [ - 'type' => 'date', + 'age' => [ + 'type' => 'INT', + 'constraint' => 10, + 'unsigned' => true, ], 'hits' => [ 'type' => 'INT', @@ -47,13 +41,7 @@ class AddAnalyticsEpisodesByCountry extends Migration 'default' => 1, ], ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey([ - 'podcast_id', - 'episode_id', - 'country_code', - 'date', - ]); + $this->forge->addPrimaryKey(['podcast_id', 'episode_id', 'date']); $this->forge->addField( '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' ); @@ -62,11 +50,11 @@ class AddAnalyticsEpisodesByCountry extends Migration ); $this->forge->addForeignKey('podcast_id', 'podcasts', 'id'); $this->forge->addForeignKey('episode_id', 'episodes', 'id'); - $this->forge->createTable('analytics_episodes_by_country'); + $this->forge->createTable('analytics_podcasts_by_episode'); } public function down() { - $this->forge->dropTable('analytics_episodes_by_country'); + $this->forge->dropTable('analytics_podcasts_by_episode'); } } diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php b/app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php similarity index 70% rename from app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php rename to app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php index 3a13f65f..c1bc04af 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_player.php +++ b/app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php @@ -17,32 +17,45 @@ class AddAnalyticsPodcastsByPlayer extends Migration public function up() { $this->forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], 'podcast_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], - 'player' => [ - 'type' => 'VARCHAR', - 'constraint' => 191, - ], 'date' => [ 'type' => 'date', ], + 'app' => [ + 'type' => 'VARCHAR', + 'constraint' => 128, + ], + 'device' => [ + 'type' => 'VARCHAR', + 'constraint' => 32, + ], + 'os' => [ + 'type' => 'VARCHAR', + 'constraint' => 32, + ], + 'bot' => [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + ], 'hits' => [ 'type' => 'INT', 'constraint' => 10, 'default' => 1, ], ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey(['podcast_id', 'player', 'date']); + $this->forge->addPrimaryKey([ + 'podcast_id', + 'app', + 'device', + 'os', + 'bot', + 'date', + ]); $this->forge->addField( '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' ); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php b/app/Database/Migrations/2020-06-08-150000_add_analytics_podcasts_by_country.php similarity index 83% rename from app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php rename to app/Database/Migrations/2020-06-08-150000_add_analytics_podcasts_by_country.php index 6545a7a1..e5f045e6 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_podcasts_by_country.php +++ b/app/Database/Migrations/2020-06-08-150000_add_analytics_podcasts_by_country.php @@ -17,33 +17,26 @@ class AddAnalyticsPodcastsByCountry extends Migration public function up() { $this->forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], 'podcast_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], + 'date' => [ + 'type' => 'date', + ], 'country_code' => [ 'type' => 'VARCHAR', 'constraint' => 3, 'comment' => 'ISO 3166-1 code.', ], - 'date' => [ - 'type' => 'date', - ], 'hits' => [ 'type' => 'INT', 'constraint' => 10, 'default' => 1, ], ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey(['podcast_id', 'country_code', 'date']); + $this->forge->addPrimaryKey(['podcast_id', 'country_code', 'date']); $this->forge->addField( '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' ); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php b/app/Database/Migrations/2020-06-08-160000_add_analytics_podcasts_by_region.php similarity index 60% rename from app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php rename to app/Database/Migrations/2020-06-08-160000_add_analytics_podcasts_by_region.php index 7f8b1415..7b787878 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_country.php +++ b/app/Database/Migrations/2020-06-08-160000_add_analytics_podcasts_by_region.php @@ -1,8 +1,8 @@ forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], 'podcast_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], + 'date' => [ + 'type' => 'date', + ], 'country_code' => [ 'type' => 'VARCHAR', 'constraint' => 3, 'comment' => 'ISO 3166-1 code.', ], - 'date' => [ - 'type' => 'date', + 'region_code' => [ + 'type' => 'VARCHAR', + 'constraint' => 3, + 'comment' => 'ISO 3166-2 code.', + ], + 'latitude' => [ + 'type' => 'FLOAT', + 'null' => true, + ], + 'longitude' => [ + 'type' => 'FLOAT', + 'null' => true, ], 'hits' => [ 'type' => 'INT', @@ -42,8 +49,12 @@ class AddAnalyticsWebsiteByCountry extends Migration 'default' => 1, ], ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey(['podcast_id', 'country_code', 'date']); + $this->forge->addPrimaryKey([ + 'podcast_id', + 'country_code', + 'region_code', + 'date', + ]); $this->forge->addField( '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' ); @@ -51,11 +62,11 @@ class AddAnalyticsWebsiteByCountry extends Migration '`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()' ); $this->forge->addForeignKey('podcast_id', 'podcasts', 'id'); - $this->forge->createTable('analytics_website_by_country'); + $this->forge->createTable('analytics_podcasts_by_region'); } public function down() { - $this->forge->dropTable('analytics_website_by_country'); + $this->forge->dropTable('analytics_podcasts_by_region'); } } diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php b/app/Database/Migrations/2020-06-08-170000_add_analytics_website_by_browser.php similarity index 82% rename from app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php rename to app/Database/Migrations/2020-06-08-170000_add_analytics_website_by_browser.php index 6e4942d4..21724af7 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_browser.php +++ b/app/Database/Migrations/2020-06-08-170000_add_analytics_website_by_browser.php @@ -17,32 +17,26 @@ class AddAnalyticsWebsiteByBrowser extends Migration public function up() { $this->forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], 'podcast_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], + 'date' => [ + 'type' => 'date', + ], 'browser' => [ 'type' => 'VARCHAR', 'constraint' => 191, ], - 'date' => [ - 'type' => 'date', - ], + 'hits' => [ 'type' => 'INT', 'constraint' => 10, 'default' => 1, ], ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey(['podcast_id', 'browser', 'date']); + $this->forge->addPrimaryKey(['podcast_id', 'browser', 'date']); $this->forge->addField( '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' ); diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php b/app/Database/Migrations/2020-06-08-180000_add_analytics_website_by_referer.php similarity index 78% rename from app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php rename to app/Database/Migrations/2020-06-08-180000_add_analytics_website_by_referer.php index 28808f27..579024b0 100644 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_website_by_referer.php +++ b/app/Database/Migrations/2020-06-08-180000_add_analytics_website_by_referer.php @@ -17,33 +17,36 @@ class AddAnalyticsWebsiteByReferer extends Migration public function up() { $this->forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], 'podcast_id' => [ 'type' => 'BIGINT', 'constraint' => 20, 'unsigned' => true, ], - 'referer' => [ - 'type' => 'VARCHAR', - 'constraint' => 191, - 'comment' => 'Referer URL.', - ], 'date' => [ 'type' => 'date', ], + 'referer' => [ + 'type' => 'VARCHAR', + 'constraint' => 512, + 'comment' => 'Referer URL.', + ], + 'domain' => [ + 'type' => 'VARCHAR', + 'constraint' => 128, + 'null' => true, + ], + 'keywords' => [ + 'type' => 'VARCHAR', + 'constraint' => 384, + 'null' => true, + ], 'hits' => [ 'type' => 'INT', 'constraint' => 10, 'default' => 1, ], ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey(['podcast_id', 'referer', 'date']); + $this->forge->addPrimaryKey(['podcast_id', 'referer', 'date']); $this->forge->addField( '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' ); diff --git a/app/Database/Migrations/2020-06-08-190000_add_analytics_website_by_entry_page.php b/app/Database/Migrations/2020-06-08-190000_add_analytics_website_by_entry_page.php new file mode 100644 index 00000000..19bce6de --- /dev/null +++ b/app/Database/Migrations/2020-06-08-190000_add_analytics_website_by_entry_page.php @@ -0,0 +1,54 @@ +forge->addField([ + 'podcast_id' => [ + 'type' => 'BIGINT', + 'constraint' => 20, + 'unsigned' => true, + ], + 'date' => [ + 'type' => 'date', + ], + 'entry_page' => [ + 'type' => 'VARCHAR', + 'constraint' => 512, + 'comment' => 'Entry page URL.', + ], + 'hits' => [ + 'type' => 'INT', + 'constraint' => 10, + 'default' => 1, + ], + ]); + $this->forge->addPrimaryKey(['podcast_id', 'entry_page', 'date']); + $this->forge->addField( + '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' + ); + $this->forge->addField( + '`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()' + ); + $this->forge->addForeignKey('podcast_id', 'podcasts', 'id'); + $this->forge->createTable('analytics_website_by_entry_page'); + } + + public function down() + { + $this->forge->dropTable('analytics_website_by_entry_page'); + } +} diff --git a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php b/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php deleted file mode 100644 index 3a1e257a..00000000 --- a/app/Database/Migrations/2020-06-08-210000_add_analytics_episodes_by_player.php +++ /dev/null @@ -1,71 +0,0 @@ -forge->addField([ - 'id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - 'auto_increment' => true, - ], - 'podcast_id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - ], - 'episode_id' => [ - 'type' => 'BIGINT', - 'constraint' => 20, - 'unsigned' => true, - ], - 'player' => [ - 'type' => 'VARCHAR', - 'constraint' => 191, - ], - 'date' => [ - 'type' => 'date', - ], - 'hits' => [ - 'type' => 'INT', - 'constraint' => 10, - 'default' => 1, - ], - ]); - $this->forge->addKey('id', true); - $this->forge->addUniqueKey([ - 'podcast_id', - 'episode_id', - 'player', - 'date', - ]); - $this->forge->addField( - '`created_at` timestamp NOT NULL DEFAULT current_timestamp()' - ); - $this->forge->addField( - '`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()' - ); - $this->forge->addForeignKey('podcast_id', 'podcasts', 'id'); - $this->forge->addForeignKey('episode_id', 'episodes', 'id'); - $this->forge->createTable('analytics_episodes_by_player'); - } - - public function down() - { - $this->forge->dropTable('analytics_episodes_by_player'); - } -} diff --git a/app/Database/Migrations/2020-06-11-210000_add_analytics_podcasts_stored_procedure.php b/app/Database/Migrations/2020-06-11-210000_add_analytics_podcasts_stored_procedure.php index 18bd203a..caf35500 100644 --- a/app/Database/Migrations/2020-06-11-210000_add_analytics_podcasts_stored_procedure.php +++ b/app/Database/Migrations/2020-06-11-210000_add_analytics_podcasts_stored_procedure.php @@ -18,26 +18,42 @@ class AddAnalyticsPodcastsStoredProcedure extends Migration { // Creates Stored Procedure for data insertion // Example: CALL analytics_podcasts(1,2,'FR','phone/android/Deezer'); - $procedureName = $this->db->prefixTable('analytics_podcasts'); - $episodesTableName = $this->db->prefixTable('analytics_episodes'); + $prefix = $this->db->getPrefix(); + $createQuery = <<db->query($createQuery); @@ -45,7 +61,9 @@ EOD; public function down() { - $procedureName = $this->db->prefixTable('analytics_podcasts'); - $this->db->query("DROP PROCEDURE IF EXISTS `$procedureName`"); + $prefix = $this->db->getPrefix(); + $this->db->query( + "DROP PROCEDURE IF EXISTS `{$prefix}analytics_podcasts`" + ); } } diff --git a/app/Database/Migrations/2020-06-11-210000_add_analytics_website_stored_procedure.php b/app/Database/Migrations/2020-06-11-210000_add_analytics_website_stored_procedure.php index c263a8a1..836b5d7d 100644 --- a/app/Database/Migrations/2020-06-11-210000_add_analytics_website_stored_procedure.php +++ b/app/Database/Migrations/2020-06-11-210000_add_analytics_website_stored_procedure.php @@ -20,20 +20,20 @@ class AddAnalyticsWebsiteStoredProcedure extends Migration // Example: CALL analytics_website(1,'FR','Firefox'); $procedureName = $this->db->prefixTable('analytics_website'); $createQuery = <<db->query($createQuery); diff --git a/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php b/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php new file mode 100644 index 00000000..e312a3a0 --- /dev/null +++ b/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php @@ -0,0 +1,176 @@ +first(); + + $jsonUserAgents = json_decode( + file_get_contents( + 'https://raw.githubusercontent.com/opawg/user-agents/master/src/user-agents.json' + ), + true + ); + + if ($podcast) { + $firstEpisode = (new EpisodeModel()) + ->selectMin('published_at') + ->first(); + + for ( + $date = strtotime($firstEpisode->published_at); + $date < strtotime('now'); + $date = strtotime(date('Y-m-d', $date) . ' +1 day') + ) { + $analytics_podcasts = []; + $analytics_podcasts_by_country = []; + $analytics_podcasts_by_episode = []; + $analytics_podcasts_by_player = []; + $analytics_podcasts_by_region = []; + + $episodes = (new EpisodeModel()) + ->where([ + 'podcast_id' => $podcast->id, + 'DATE(published_at) <=' => date('Y-m-d', $date), + ]) + ->findAll(); + foreach ($episodes as $episode) { + $age = floor( + ($date - strtotime($episode->published_at)) / 86400 + ); + $proba1 = floor(exp(3 - $age / 40)) + 1; + + for ( + $num_line = 0; + $num_line < rand(1, $proba1); + $num_line++ + ) { + $proba2 = floor(exp(6 - $age / 20)) + 10; + + $player = + $jsonUserAgents[ + rand(1, count($jsonUserAgents) - 1) + ]; + $app = isset($player['app']) ? $player['app'] : ''; + $device = isset($player['device']) + ? $player['device'] + : ''; + $os = isset($player['os']) ? $player['os'] : ''; + $bot = isset($player['bot']) ? $player['bot'] : 0; + + $fakeIp = + rand(0, 255) . + '.' . + rand(0, 255) . + '.' . + rand(0, 255) . + '.' . + rand(0, 255); + + $cityReader = new \GeoIp2\Database\Reader( + WRITEPATH . + 'uploads/GeoLite2-City/GeoLite2-City.mmdb' + ); + + $countryCode = 'N/A'; + $regionCode = 'N/A'; + $latitude = null; + $longitude = null; + try { + $city = $cityReader->city($fakeIp); + + $countryCode = empty($city->country->isoCode) + ? 'N/A' + : $city->country->isoCode; + + $regionCode = empty($city->subdivisions[0]->isoCode) + ? 'N/A' + : $city->subdivisions[0]->isoCode; + $latitude = round($city->location->latitude, 3); + $longitude = round($city->location->longitude, 3); + } catch (\GeoIp2\Exception\AddressNotFoundException $ex) { + //Bad luck, bad IP, nothing to do. + } + + $hits = rand(0, $proba2); + + $analytics_podcasts[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'hits' => $hits, + ]; + $analytics_podcasts_by_country[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'country_code' => $countryCode, + 'hits' => $hits, + ]; + $analytics_podcasts_by_episode[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'episode_id' => $episode->id, + 'age' => $age, + 'hits' => $hits, + ]; + $analytics_podcasts_by_player[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'app' => $app, + 'device' => $device, + 'os' => $os, + 'bot' => $bot, + 'hits' => $hits, + ]; + $analytics_podcasts_by_region[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'country_code' => $countryCode, + 'region_code' => $regionCode, + 'latitude' => $latitude, + 'longitude' => $longitude, + 'hits' => $hits, + ]; + } + } + $this->db + ->table('analytics_podcasts') + ->ignore(true) + ->insertBatch($analytics_podcasts); + $this->db + ->table('analytics_podcasts_by_country') + ->ignore(true) + ->insertBatch($analytics_podcasts_by_country); + $this->db + ->table('analytics_podcasts_by_episode') + ->ignore(true) + ->insertBatch($analytics_podcasts_by_episode); + $this->db + ->table('analytics_podcasts_by_player') + ->ignore(true) + ->insertBatch($analytics_podcasts_by_player); + $this->db + ->table('analytics_podcasts_by_region') + ->ignore(true) + ->insertBatch($analytics_podcasts_by_region); + } + } else { + echo "Create one podcast and some episodes first.\n"; + } + } +} diff --git a/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php b/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php new file mode 100644 index 00000000..67270d4c --- /dev/null +++ b/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php @@ -0,0 +1,260 @@ +first(); + + if ($podcast) { + $firstEpisode = (new EpisodeModel()) + ->selectMin('published_at') + ->first(); + + for ( + $date = strtotime($firstEpisode->published_at); + $date < strtotime('now'); + $date = strtotime(date('Y-m-d', $date) . ' +1 day') + ) { + $website_by_browser = []; + $website_by_entry_page = []; + $website_by_referer = []; + + $episodes = (new EpisodeModel()) + ->where([ + 'podcast_id' => $podcast->id, + 'DATE(published_at) <=' => date('Y-m-d', $date), + ]) + ->findAll(); + foreach ($episodes as $episode) { + $age = floor( + ($date - strtotime($episode->published_at)) / 86400 + ); + $proba1 = floor(exp(3 - $age / 40)) + 1; + + for ( + $num_line = 0; + $num_line < rand(1, $proba1); + $num_line++ + ) { + $proba2 = floor(exp(6 - $age / 20)) + 10; + + $domain = + $this->domains[rand(0, count($this->domains) - 1)]; + $keyword = + $this->keywords[ + rand(0, count($this->keywords) - 1) + ]; + $browser = + $this->browsers[ + rand(0, count($this->browsers) - 1) + ]; + + $hits = rand(0, $proba2); + + $website_by_browser[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'browser' => $browser, + 'hits' => $hits, + ]; + $website_by_entry_page[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'entry_page' => $episode->link, + 'hits' => $hits, + ]; + $website_by_referer[] = [ + 'podcast_id' => $podcast->id, + 'date' => date('Y-m-d', $date), + 'referer' => + 'http://' . $domain . '/?q=' . $keyword, + 'domain' => $domain, + 'keywords' => $keyword, + 'hits' => $hits, + ]; + } + } + $this->db + ->table('analytics_website_by_browser') + ->ignore(true) + ->insertBatch($website_by_browser); + $this->db + ->table('analytics_website_by_entry_page') + ->ignore(true) + ->insertBatch($website_by_entry_page); + $this->db + ->table('analytics_website_by_referer') + ->ignore(true) + ->insertBatch($website_by_referer); + } + } else { + echo "Create one podcast and some episodes first.\n"; + } + } +} diff --git a/app/Entities/AnalyticsWebsiteByCountry.php b/app/Entities/AnalyticsPodcasts.php similarity index 67% rename from app/Entities/AnalyticsWebsiteByCountry.php rename to app/Entities/AnalyticsPodcasts.php index 9839e3cb..7f0f169e 100644 --- a/app/Entities/AnalyticsWebsiteByCountry.php +++ b/app/Entities/AnalyticsPodcasts.php @@ -1,8 +1,8 @@ 'integer', - 'country_code' => 'string', 'date' => 'datetime', 'hits' => 'integer', ]; diff --git a/app/Entities/AnalyticsEpisodesByPlayer.php b/app/Entities/AnalyticsPodcastsByEpisode.php similarity index 70% rename from app/Entities/AnalyticsEpisodesByPlayer.php rename to app/Entities/AnalyticsPodcastsByEpisode.php index 3e48c0aa..783bf2d5 100644 --- a/app/Entities/AnalyticsEpisodesByPlayer.php +++ b/app/Entities/AnalyticsPodcastsByEpisode.php @@ -1,8 +1,8 @@ 'integer', 'episode_id' => 'integer', - 'player' => 'string', 'date' => 'datetime', 'hits' => 'integer', ]; diff --git a/app/Entities/AnalyticsPodcastsByPlayer.php b/app/Entities/AnalyticsPodcastsByPlayer.php index 9e33ba98..b0e19d26 100644 --- a/app/Entities/AnalyticsPodcastsByPlayer.php +++ b/app/Entities/AnalyticsPodcastsByPlayer.php @@ -16,7 +16,10 @@ class AnalyticsPodcastsByPlayer extends Entity { protected $casts = [ 'podcast_id' => 'integer', - 'player' => 'string', + 'app' => '?string', + 'device' => '?string', + 'os' => '?string', + 'bot' => 'boolean', 'date' => 'datetime', 'hits' => 'integer', ]; diff --git a/app/Entities/AnalyticsPodcastsByRegion.php b/app/Entities/AnalyticsPodcastsByRegion.php new file mode 100644 index 00000000..8f6a9d60 --- /dev/null +++ b/app/Entities/AnalyticsPodcastsByRegion.php @@ -0,0 +1,26 @@ + 'integer', + 'country_code' => 'string', + 'region_code' => '?string', + 'latitude' => '?float', + 'longitude' => '?float', + 'date' => 'datetime', + 'hits' => 'integer', + ]; +} diff --git a/app/Entities/AnalyticsEpisodesByCountry.php b/app/Entities/AnalyticsWebsiteByEntryPage.php similarity index 62% rename from app/Entities/AnalyticsEpisodesByCountry.php rename to app/Entities/AnalyticsWebsiteByEntryPage.php index b1736443..344d60fb 100644 --- a/app/Entities/AnalyticsEpisodesByCountry.php +++ b/app/Entities/AnalyticsWebsiteByEntryPage.php @@ -1,8 +1,8 @@ 'integer', - 'episode_id' => 'integer', - 'country_code' => 'string', + 'entry_page' => '?string', 'date' => 'datetime', 'hits' => 'integer', ]; diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 7bbda5e5..26f0ee5c 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -64,6 +64,7 @@ class Episode extends Entity 'enclosure_duration' => 'integer', 'enclosure_mimetype' => 'string', 'enclosure_filesize' => 'integer', + 'enclosure_headersize' => 'integer', 'description' => 'string', 'image_uri' => '?string', 'parental_advisory' => '?string', @@ -143,6 +144,8 @@ class Episode extends Entity $enclosure_metadata['mime_type']; $this->attributes['enclosure_filesize'] = $enclosure_metadata['filesize']; + $this->attributes['enclosure_headersize'] = + $enclosure_metadata['avdataoffset']; return $this; } @@ -167,6 +170,19 @@ class Episode extends Entity 'analytics_hit', $this->attributes['podcast_id'], $this->attributes['id'], + // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics + // - if file is shorter than 60sec, then it's enclosure_filesize + // - if file is longer than 60 seconds then it's enclosure_headersize + 60 seconds + $this->attributes['enclosure_duration'] <= 60 + ? $this->attributes['enclosure_filesize'] + : $this->attributes['enclosure_headersize'] + + floor( + (($this->attributes['enclosure_filesize'] - + $this->attributes['enclosure_headersize']) / + $this->attributes['enclosure_duration']) * + 60 + ), + $this->attributes['enclosure_filesize'], $this->attributes['enclosure_uri'] ) ); diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index c0dd2b7d..f60e44dc 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -82,6 +82,7 @@ class Podcast extends Entity 'created_by' => 'integer', 'updated_by' => 'integer', 'imported_feed_url' => '?string', + 'new_feed_url' => '?string', ]; /** diff --git a/app/Helpers/analytics_helper.php b/app/Helpers/analytics_helper.php index d0ca06cb..51561e95 100644 --- a/app/Helpers/analytics_helper.php +++ b/app/Helpers/analytics_helper.php @@ -33,25 +33,56 @@ if (!function_exists('getallheaders')) { /** * Set user country in session variable, for analytics purpose */ -function set_user_session_country() +function set_user_session_deny_list_ip() { $session = \Config\Services::session(); $session->start(); - $country = 'N/A'; + if (!$session->has('denyListIp')) { + $session->set( + 'denyListIp', + \Podlibre\Ipcat\IpDb::find($_SERVER['REMOTE_ADDR']) != null + ); + } +} - // Finds country: - if (!$session->has('country')) { +/** + * Set user country in session variable, for analytics purpose + */ +function set_user_session_location() +{ + $session = \Config\Services::session(); + $session->start(); + + $location = [ + 'countryCode' => 'N/A', + 'regionCode' => 'N/A', + 'latitude' => null, + 'longitude' => null, + ]; + + // Finds location: + if (!$session->has('location')) { try { - $reader = new \GeoIp2\Database\Reader( - WRITEPATH . 'uploads/GeoLite2-Country/GeoLite2-Country.mmdb' + $cityReader = new \GeoIp2\Database\Reader( + WRITEPATH . 'uploads/GeoLite2-City/GeoLite2-City.mmdb' ); - $geoip = $reader->country($_SERVER['REMOTE_ADDR']); - $country = $geoip->country->isoCode; + $city = $cityReader->city($_SERVER['REMOTE_ADDR']); + + $location = [ + 'countryCode' => empty($city->country->isoCode) + ? 'N/A' + : $city->country->isoCode, + 'regionCode' => empty($city->subdivisions[0]->isoCode) + ? 'N/A' + : $city->subdivisions[0]->isoCode, + 'latitude' => round($city->location->latitude, 3), + 'longitude' => round($city->location->longitude, 3), + ]; } catch (\Exception $e) { // If things go wrong the show must go on and the user must be able to download the file } - $session->set('country', $country); + $session->set('location', $location); } } @@ -67,58 +98,36 @@ function set_user_session_player() $session = \Config\Services::session(); $session->start(); - $playerName = '- Unknown Player -'; - - $useragent = $_SERVER['HTTP_USER_AGENT']; + $playerFound = null; + $userAgent = $_SERVER['HTTP_USER_AGENT']; try { - $jsonUserAgents = json_decode( - file_get_contents( - WRITEPATH . 'uploads/user-agents/src/user-agents.json' - ), - true - ); - - //Search for current HTTP_USER_AGENT in json file: - foreach ($jsonUserAgents as $player) { - foreach ($player['user_agents'] as $useragentsRegexp) { - //Does the HTTP_USER_AGENT match this regexp: - if (preg_match("#{$useragentsRegexp}#", $useragent)) { - if (isset($player['bot'])) { - //It’s a bot! - $playerName = '- Bot -'; - } else { - //It isn’t a bot, we store device/os/app: - $playerName = - (isset($player['device']) - ? $player['device'] . '/' - : '') . - (isset($player['os']) - ? $player['os'] . '/' - : '') . - (isset($player['app']) ? $player['app'] : '?'); - } - //We found it! - break 2; - } - } - } + $playerFound = \Podlibre\UserAgentsPhp\UserAgents::find($userAgent); } catch (\Exception $e) { // If things go wrong the show must go on and the user must be able to download the file } - if ($playerName == '- Unknown Player -') { + if ($playerFound) { + $session->set('player', $playerFound); + } else { + $session->set('player', [ + 'app' => '- unknown -', + 'device' => '', + 'os' => '', + 'bot' => 0, + ]); // Add to unknown list try { $db = \Config\Database::connect(); - $procedureNameAUU = $db->prefixTable( + $procedureNameAnalyticsUnknownUseragents = $db->prefixTable( 'analytics_unknown_useragents' ); - $db->query("CALL $procedureNameAUU(?)", [$useragent]); + $db->query("CALL $procedureNameAnalyticsUnknownUseragents(?)", [ + $userAgent, + ]); } catch (\Exception $e) { // If things go wrong the show must go on and the user must be able to download the file } } - $session->set('player', $playerName); } } @@ -165,49 +174,149 @@ function set_user_session_referer() } } +/** + * Set user entry page in session variable, for analytics purpose + */ +function set_user_session_entry_page() +{ + $session = \Config\Services::session(); + $session->start(); + + $entryPage = $_SERVER['REQUEST_URI']; + if (!$session->has('entryPage')) { + $session->set('entryPage', $entryPage); + } +} + function webpage_hit($podcast_id) { $session = \Config\Services::session(); $session->start(); - $db = \Config\Database::connect(); - $procedureName = $db->prefixTable('analytics_website'); - $db->query("call $procedureName(?,?,?,?)", [ - $podcast_id, - $session->get('country'), - $session->get('browser'), - $session->get('referer'), - ]); + if (!$session->get('denyListIp')) { + $db = \Config\Database::connect(); + + $referer = $session->get('referer'); + $domain = empty(parse_url($referer, PHP_URL_HOST)) + ? null + : parse_url($referer, PHP_URL_HOST); + parse_str(parse_url($referer, PHP_URL_QUERY), $queries); + $keywords = empty($queries['q']) ? null : $queries['q']; + + $procedureName = $db->prefixTable('analytics_website'); + $db->query("call $procedureName(?,?,?,?,?,?)", [ + $podcast_id, + $session->get('browser'), + $session->get('entryPage'), + $referer, + $domain, + $keywords, + ]); + } } -function podcast_hit($p_podcast_id, $p_episode_id) +/** + * Counting podcast episode downloads for analytics purposes + * ✅ No IP address is ever stored on the server. + * ✅ Only aggregate data is stored in the database. + * We follow IAB Podcast Measurement Technical Guidelines Version 2.0: + * https://iabtechlab.com/standards/podcast-measurement-guidelines/ + * https://iabtechlab.com/wp-content/uploads/2017/12/Podcast_Measurement_v2-Dec-20-2017.pdf + * ✅ Rolling 24-hour window + * ✅ Castopod does not do pre-load + * ✅ IP deny list https://github.com/client9/ipcat + * ✅ User-agent Filtering https://github.com/opawg/user-agents + * ✅ Ignores 2 bytes range "Range: 0-1" (performed by official Apple iOS Podcast app) + * ✅ In case of partial content, adds up all requests to check >1mn was downloaded + * ✅ Identifying Uniques is done with a combination of IP Address and User Agent + * @param int $podcastId The podcast ID + * @param int $episodeId The Episode ID + * @param int $bytesThreshold The minimum total number of bytes that must be downloaded so that an episode is counted (>1mn) + * @param int $fileSize The podcast complete file size + * + * @return void + */ +function podcast_hit($podcastId, $episodeId, $bytesThreshold, $fileSize) { $session = \Config\Services::session(); $session->start(); - $first_time_for_this_episode = true; - if ($session->has('episodes')) { - if (in_array($p_episode_id, $session->get('episodes'))) { - $first_time_for_this_episode = false; + // We try to count (but if things went wrong the show should go on and the user should be able to download the file): + try { + // If the user IP is denied it's probably a bot: + if ($session->get('denyListIp')) { + $session->get('player')['bot'] = true; + } + $httpRange = $_SERVER['HTTP_RANGE']; + // We create a sha1 hash for this IP_Address+User_Agent+Episode_ID: + $hashID = + '_IpUaEp_' . + sha1( + $_SERVER['REMOTE_ADDR'] . + '_' . + $_SERVER['HTTP_USER_AGENT'] . + '_' . + $episodeId + ); + // Was this episode downloaded in the past 24h: + $downloadedBytes = cache($hashID); + // Rolling window is 24 hours (86400 seconds): + $ttl = 86400; + if ($downloadedBytes) { + // In case it was already downloaded, TTL should be adjusted (rolling window is 24h since 1st download): + $ttl = cache()->getMetadata($hashID)['expire'] - time(); } else { - $session->push('episodes', [$p_episode_id]); + // If it was never downloaded that means that zero byte were downloaded: + $downloadedBytes = 0; } - } else { - $session->set('episodes', [$p_episode_id]); - } + // If the number of downloaded bytes was previously below the 1mn threshold we go on: + // (Otherwise it means that this was already counted, therefore we don't do anything) + if ($downloadedBytes < $bytesThreshold) { + // If HTTP_RANGE is null we are downloading the complete file: + if (!isset($httpRange)) { + $downloadedBytes = $fileSize; + } else { + // [0-1] bytes range requests are used (by Apple) to check that file exists and that 206 partial content is working. + // We don't count these requests: + if ($httpRange != 'bytes=0-1') { + // We calculate how many bytes are being downloaded based on HTTP_RANGE values: + $ranges = explode(',', substr($httpRange, 6)); + foreach ($ranges as $range) { + $parts = explode('-', $range); + $downloadedBytes += empty($parts[1]) + ? $fileSize + : $parts[1] - (empty($parts[0]) ? 0 : $parts[0]); + } + } + } + // We save the number of downloaded bytes for this user and this episode: + cache()->save($hashID, $downloadedBytes, $ttl); - if ($first_time_for_this_episode) { - $db = \Config\Database::connect(); - $procedureName = $db->prefixTable('analytics_podcasts'); - try { - $db->query("CALL $procedureName(?,?,?,?);", [ - $p_podcast_id, - $p_episode_id, - $session->get('country'), - $session->get('player'), - ]); - } catch (\Exception $e) { - // If things go wrong the show must go on and the user must be able to download the file + // If more that 1mn was downloaded, we send that to the database: + if ($downloadedBytes >= $bytesThreshold) { + $db = \Config\Database::connect(); + $procedureName = $db->prefixTable('analytics_podcasts'); + + $app = $session->get('player')['app']; + $device = $session->get('player')['device']; + $os = $session->get('player')['os']; + $bot = $session->get('player')['bot']; + + $db->query("CALL $procedureName(?,?,?,?,?,?,?,?,?,?);", [ + $podcastId, + $episodeId, + $session->get('location')['countryCode'], + $session->get('location')['regionCode'], + $session->get('location')['latitude'], + $session->get('location')['longitude'], + $app == null ? '' : $app, + $device == null ? '' : $device, + $os == null ? '' : $os, + $bot == null ? 0 : $bot, + ]); + } } + } catch (\Exception $e) { + // If things go wrong the show must go on and the user must be able to download the file } } diff --git a/app/Helpers/id3_helper.php b/app/Helpers/id3_helper.php index a7c1685a..61c21e2e 100644 --- a/app/Helpers/id3_helper.php +++ b/app/Helpers/id3_helper.php @@ -24,6 +24,7 @@ function get_file_tags($file) return [ 'filesize' => $FileInfo['filesize'], 'mime_type' => $FileInfo['mime_type'], + 'avdataoffset' => $FileInfo['avdataoffset'], 'playtime_seconds' => $FileInfo['playtime_seconds'], ]; } @@ -68,7 +69,11 @@ function write_enclosure_tags($episode) 'comment' => [$episode->description], 'track_number' => [strval($episode->number)], 'copyright_message' => [$episode->podcast->copyright], - 'publisher' => ['Podlibre'], + 'publisher' => [ + empty($episode->podcast->publisher) + ? $episode->podcast->owner_name + : $episode->podcast->publisher, + ], 'encoded_by' => ['Castopod'], // TODO: find a way to add the remaining tags for podcasts as the library doesn't seem to allow it diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 532b9bcb..9f17a2f7 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -36,6 +36,14 @@ function get_rss_feed($podcast) $atom_link->addAttribute('rel', 'self'); $atom_link->addAttribute('type', 'application/rss+xml'); + if (!empty($podcast->new_feed_url)) { + $channel->addChild( + 'new-feed-url', + $podcast->new_feed_url, + $itunes_namespace + ); + } + // the last build date corresponds to the creation of the feed.xml cache $channel->addChild( 'lastBuildDate', @@ -50,7 +58,7 @@ function get_rss_feed($podcast) $channel->addChild('title', $podcast->title); $channel->addChildWithCDATA('description', $podcast->description_html); $itunes_image = $channel->addChild('image', null, $itunes_namespace); - $itunes_image->addAttribute('href', $podcast->image->url); + $itunes_image->addAttribute('href', $podcast->image->original_url); $channel->addChild('language', $podcast->language); // set main category first, then other categories as apple diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php index 70e5beb9..5827731b 100644 --- a/app/Language/en/Breadcrumb.php +++ b/app/Language/en/Breadcrumb.php @@ -22,4 +22,5 @@ return [ 'import' => 'feed import', 'settings' => 'settings', 'platforms' => 'platforms', + 'analytics' => 'Analytics', ]; diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index 8a86df06..fc54298c 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -12,7 +12,7 @@ return [ 'create' => 'Create a podcast', 'import' => 'Import a podcast', 'new_episode' => 'New Episode', - 'feed' => 'RSS feed', + 'feed' => 'RSS', 'view' => 'View podcast', 'edit' => 'Edit podcast', 'delete' => 'Delete podcast', diff --git a/app/Language/en/PodcastImport.php b/app/Language/en/PodcastImport.php index 6b86eb16..3bb3a912 100644 --- a/app/Language/en/PodcastImport.php +++ b/app/Language/en/PodcastImport.php @@ -7,6 +7,12 @@ */ return [ + 'legal_dislaimer_title' => 'Legal Disclaimer', + 'legal_dislaimer_content' => + 'Make sure you own the rights for this podcast before importing it.
Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'warning_title' => 'Warning', + 'warning_content' => + 'This procedure may take a long time.
The current version does not show any progress while it runs. You will not see anything updated until it is done.
In case of timeout error, increase max_execution_time value.', 'old_podcast_section_title' => 'The podcast to import', 'old_podcast_section_subtitle' => '', 'imported_feed_url' => 'Feed URL', diff --git a/app/Language/en/PodcastNavigation.php b/app/Language/en/PodcastNavigation.php index 05fe31f1..3d153206 100644 --- a/app/Language/en/PodcastNavigation.php +++ b/app/Language/en/PodcastNavigation.php @@ -20,4 +20,5 @@ return [ 'contributor-add' => 'Add contributor', 'settings' => 'Settings', 'platforms' => 'Podcast platforms', + 'podcast-analytics' => 'Audiences Overview', ]; diff --git a/app/Models/AnalyticsPodcastsByCountryModel.php b/app/Models/AnalyticsPodcastsByCountryModel.php index 70f5fc3e..4f209453 100644 --- a/app/Models/AnalyticsPodcastsByCountryModel.php +++ b/app/Models/AnalyticsPodcastsByCountryModel.php @@ -2,7 +2,7 @@ /** * Class AnalyticsPodcastsByCountryModel - * Model for analytics_episodes_by_country table in database + * Model for analytics_podcasts_by_country table in database * @copyright 2020 Podlibre * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ @@ -14,8 +14,7 @@ use CodeIgniter\Model; class AnalyticsPodcastsByCountryModel extends Model { - protected $table = 'analytics_episodes_by_country'; - protected $primaryKey = 'id'; + protected $table = 'analytics_podcasts_by_country'; protected $allowedFields = []; diff --git a/app/Models/AnalyticsPodcastsByEpisodeModel.php b/app/Models/AnalyticsPodcastsByEpisodeModel.php new file mode 100644 index 00000000..59c82360 --- /dev/null +++ b/app/Models/AnalyticsPodcastsByEpisodeModel.php @@ -0,0 +1,113 @@ +select('id, season_number, number, title') + ->orderBy('id', 'DESC') + ->where(['podcast_id' => $podcastId]) + ->findAll(5); + + $found = $this->select('age AS X'); + + $letter = 97; + foreach ($lastEpisodes as $episode) { + $found = $found + ->selectSum( + '(CASE WHEN `episode_id`=' . + $episode->id . + ' THEN `hits` END)', + chr($letter) . 'Y' + ) + ->select( + '"' . + (empty($episode->season_number) + ? '' + : $episode->season_number) . + (empty($episode->number) + ? '' + : '-' . $episode->number . '/ ') . + $episode->title . + '" AS ' . + chr($letter) . + 'Value' + ); + $letter++; + } + + $found = $found + ->where([ + 'podcast_id' => $podcastId, + 'age <' => 60, + ]) + ->groupBy('X') + ->orderBy('X', 'ASC') + ->findAll(); + + cache()->save( + "{$podcastId}_analytics_podcast_by_episode_by_day", + $found, + 14400 + ); + } + return $found; + } else { + if ( + !($found = cache( + "{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_day" + )) + ) { + $found = $this->select('date as labels') + ->selectSum('hits', 'values') + ->where([ + 'episode_id' => $episodeId, + 'podcast_id' => $podcastId, + ]) + ->groupBy('labels') + ->orderBy('labels', 'ASC') + ->findAll(); + + cache()->save( + "{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_day", + $found, + 14400 + ); + } + return $found; + } + } +} diff --git a/app/Models/AnalyticsPodcastsByPlayerModel.php b/app/Models/AnalyticsPodcastsByPlayerModel.php index 5e0ff822..900b44fa 100644 --- a/app/Models/AnalyticsPodcastsByPlayerModel.php +++ b/app/Models/AnalyticsPodcastsByPlayerModel.php @@ -15,7 +15,6 @@ use CodeIgniter\Model; class AnalyticsPodcastsByPlayerModel extends Model { protected $table = 'analytics_podcasts_by_player'; - protected $primaryKey = 'id'; protected $allowedFields = []; @@ -23,4 +22,120 @@ class AnalyticsPodcastsByPlayerModel extends Model protected $useSoftDeletes = false; protected $useTimestamps = false; + + /** + * Gets all data for a podcast + * + * @param int $podcastId + * + * @return array + */ + public function getDataByApp(int $podcastId): array + { + if ( + !($found = cache( + "{$podcastId}_analytics_podcasts_by_player_by_app" + )) + ) { + $found = $this->select('`app` as `labels`') + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`app` !=' => null, + '`bot`' => 0, + '`date` >' => date('Y-m-d', strtotime('-1 week')), + ]) + ->groupBy('`labels`') + ->orderBy('`values``', 'DESC') + ->findAll(10); + + cache()->save( + "{$podcastId}_analytics_podcasts_by_player_by_app", + $found, + 14400 + ); + } + + return $found; + } + + /** + * Gets all data for a podcast + * + * @param int $podcastId + * + * @return array + */ + public function getDataByDevice(int $podcastId): array + { + if ( + !($found = cache( + "{$podcastId}_analytics_podcasts_by_player_by_device" + )) + ) { + $foundApp = $this->select( + 'CONCAT_WS("/", `device`, `os`, `app`) as `ids`, `app` as `labels`, CONCAT_WS("/", `device`, `os`) as `parents`' + ) + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`app` !=' => null, + '`bot`' => 0, + '`date` >' => date('Y-m-d', strtotime('-1 week')), + ]) + ->groupBy('`ids`') + ->orderBy('`values``', 'DESC') + ->findAll(); + + $foundOs = $this->select( + 'CONCAT_WS("/", `device`, `os`) as `ids`, `os` as `labels`, `device` as `parents`' + ) + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`os` !=' => null, + '`bot`' => 0, + '`date` >' => date('Y-m-d', strtotime('-1 week')), + ]) + ->groupBy('`ids`') + ->orderBy('`values``', 'DESC') + ->findAll(); + + $foundDevice = $this->select( + '`device` as `ids`, `device` as `labels`, "" as `parents`' + ) + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`device` !=' => null, + '`bot`' => 0, + '`date` >' => date('Y-m-d', strtotime('-1 week')), + ]) + ->groupBy('`ids`') + ->orderBy('`values``', 'DESC') + ->findAll(); + + $foundBot = $this->select( + '"bots" as `ids`, "Bots" as `labels`, "" as `parents`' + ) + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`bot`' => 1, + '`date` >' => date('Y-m-d', strtotime('-1 week')), + ]) + ->groupBy('`ids`') + ->orderBy('`values``', 'DESC') + ->findAll(); + + $found = array_merge($foundApp, $foundOs, $foundDevice, $foundBot); + cache()->save( + "{$podcastId}_analytics_podcasts_by_player_by_device", + $found, + 14400 + ); + } + + return $found; + } } diff --git a/app/Models/AnalyticsPodcastsByRegionModel.php b/app/Models/AnalyticsPodcastsByRegionModel.php new file mode 100644 index 00000000..81ab8537 --- /dev/null +++ b/app/Models/AnalyticsPodcastsByRegionModel.php @@ -0,0 +1,25 @@ +select('`date` as `labels`') + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`date` >' => date('Y-m-d', strtotime('-1 year')), + ]) + ->groupBy('`labels`') + ->orderBy('`labels``', 'ASC') + ->findAll(); + + cache()->save( + "{$podcastId}_analytics_podcast_by_day", + $found, + 14400 + ); + } + + return $found; + } +} diff --git a/app/Models/AnalyticsWebsiteByBrowserModel.php b/app/Models/AnalyticsWebsiteByBrowserModel.php index ceee6b3e..85b4fc92 100644 --- a/app/Models/AnalyticsWebsiteByBrowserModel.php +++ b/app/Models/AnalyticsWebsiteByBrowserModel.php @@ -15,7 +15,6 @@ use CodeIgniter\Model; class AnalyticsWebsiteByBrowserModel extends Model { protected $table = 'analytics_website_by_browser'; - protected $primaryKey = 'id'; protected $allowedFields = []; diff --git a/app/Models/AnalyticsWebsiteByEntryPageModel.php b/app/Models/AnalyticsWebsiteByEntryPageModel.php new file mode 100644 index 00000000..6e7cfa0c --- /dev/null +++ b/app/Models/AnalyticsWebsiteByEntryPageModel.php @@ -0,0 +1,25 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/app/Views/_assets/images/logo-castopod.svg b/app/Views/_assets/images/logo-castopod.svg index 191b6cc9..0208232a 100644 --- a/app/Views/_assets/images/logo-castopod.svg +++ b/app/Views/_assets/images/logo-castopod.svg @@ -1,86 +1,40 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/app/Views/_assets/images/platforms/_default.svg b/app/Views/_assets/images/platforms/_default.svg index b7c7127f..42640a76 100644 --- a/app/Views/_assets/images/platforms/_default.svg +++ b/app/Views/_assets/images/platforms/_default.svg @@ -1,7 +1,26 @@ - - - - - - + +image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/app/Views/_assets/modules/Charts.ts b/app/Views/_assets/modules/Charts.ts new file mode 100644 index 00000000..61acd361 --- /dev/null +++ b/app/Views/_assets/modules/Charts.ts @@ -0,0 +1,134 @@ +// Import modules +import * as am4charts from "@amcharts/amcharts4/charts"; +import * as am4core from "@amcharts/amcharts4/core"; +import am4themes_material from "@amcharts/amcharts4/themes/material"; + +const drawPieChart = (chartDivId: string, dataUrl: string | null): void => { + // Create chart instance + const chart = am4core.create(chartDivId, am4charts.PieChart); + am4core.percent(100); + + // Set theme + am4core.useTheme(am4themes_material); + + chart.innerRadius = am4core.percent(10); + + // Add data + chart.dataSource.url = dataUrl || ""; + chart.dataSource.parser.options.emptyAs = 0; + + // Add and configure Series + const pieSeries = chart.series.push(new am4charts.PieSeries()); + pieSeries.dataFields.value = "values"; + pieSeries.dataFields.category = "labels"; + + pieSeries.slices.template.stroke = am4core.color("#ffffff"); + pieSeries.slices.template.strokeWidth = 1; + pieSeries.slices.template.strokeOpacity = 1; + pieSeries.labels.template.disabled = true; + pieSeries.ticks.template.disabled = true; + + chart.legend = new am4charts.Legend(); + chart.legend.position = "right"; + chart.legend.scrollable = true; +}; + +const drawXYChart = (chartDivId: string, dataUrl: string | null): void => { + // Create chart instance + const chart = am4core.create(chartDivId, am4charts.XYChart); + am4core.percent(100); + + // Set theme + am4core.useTheme(am4themes_material); + + // Create axes + const dateAxis = chart.xAxes.push(new am4charts.DateAxis()); + dateAxis.renderer.minGridDistance = 60; + + chart.yAxes.push(new am4charts.ValueAxis()); + + // Add data + chart.dataSource.url = dataUrl || ""; + chart.dataSource.parser.options.emptyAs = 0; + + // Create series + const series = chart.series.push(new am4charts.LineSeries()); + series.dataFields.valueY = "values"; + series.dataFields.dateX = "labels"; + series.tooltipText = "{valueY} downloads"; + + series.tooltip.pointerOrientation = "vertical"; + + chart.cursor = new am4charts.XYCursor(); + chart.cursor.snapToSeries = series; + chart.cursor.xAxis = dateAxis; + + chart.scrollbarX = new am4core.Scrollbar(); +}; + +const drawXYSeriesChart = ( + chartDivId: string, + dataUrl: string | null +): void => { + // Create chart instance + const chart = am4core.create(chartDivId, am4charts.XYChart); + am4core.percent(100); + + // Set theme + am4core.useTheme(am4themes_material); + + // Create axes + chart.xAxes.push(new am4charts.ValueAxis()); + chart.yAxes.push(new am4charts.ValueAxis()); + + // Add data + chart.dataSource.url = dataUrl || ""; + chart.dataSource.parser.options.emptyAs = 0; + + // Create series + const series1 = chart.series.push(new am4charts.LineSeries()); + series1.dataFields.valueX = "X"; + series1.dataFields.valueY = "aY"; + + const series2 = chart.series.push(new am4charts.LineSeries()); + series2.dataFields.valueX = "X"; + series2.dataFields.valueY = "bY"; + + const series3 = chart.series.push(new am4charts.LineSeries()); + series3.dataFields.valueX = "X"; + series3.dataFields.valueY = "cY"; + + const series4 = chart.series.push(new am4charts.LineSeries()); + series4.dataFields.valueX = "X"; + series4.dataFields.valueY = "dY"; + + const series5 = chart.series.push(new am4charts.LineSeries()); + series5.dataFields.valueX = "X"; + series5.dataFields.valueY = "eY"; +}; + +const DrawCharts = (): void => { + const chartDivs: NodeListOf = document.querySelectorAll( + "div[data-chart-type]" + ); + + for (let i = 0; i < chartDivs.length; i++) { + const chartDiv: HTMLDivElement = chartDivs[i]; + const chartType = chartDiv.dataset.chartType; + switch (chartType) { + case "pie-chart": + drawPieChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); + break; + case "xy-chart": + drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); + break; + case "xy-series-chart": + drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); + break; + default: + console.error("Unknown chart type:" + chartType); + } + } +}; + +export default DrawCharts; diff --git a/app/Views/_layout.php b/app/Views/_layout.php index c2ce4918..dd3ea6e9 100644 --- a/app/Views/_layout.php +++ b/app/Views/_layout.php @@ -24,7 +24,7 @@ - 'Castopod', + 'Castopod', ]) ?> diff --git a/app/Views/admin/_layout.php b/app/Views/admin/_layout.php index 8cd3c3f1..d18c2459 100644 --- a/app/Views/admin/_layout.php +++ b/app/Views/admin/_layout.php @@ -9,7 +9,7 @@ - + @@ -43,12 +43,12 @@ diff --git a/app/Views/admin/podcast/_sidebar.php b/app/Views/admin/podcast/_sidebar.php index 23bc1f3f..570008cc 100644 --- a/app/Views/admin/podcast/_sidebar.php +++ b/app/Views/admin/podcast/_sidebar.php @@ -10,7 +10,7 @@ $podcastNavigation = [ ], 'analytics' => [ 'icon' => 'line-chart', - 'items' => [], + 'items' => ['podcast-analytics'], ], 'contributors' => [ 'icon' => 'group', diff --git a/app/Views/admin/podcast/analytics.php b/app/Views/admin/podcast/analytics.php new file mode 100644 index 00000000..c57559b6 --- /dev/null +++ b/app/Views/admin/podcast/analytics.php @@ -0,0 +1,32 @@ +extend('admin/_layout') ?> + +section('title') ?> +title ?> +endSection() ?> + +section('pageTitle') ?> +title ?> +endSection() ?> + +section('content') ?> +
+
+
+ + +endSection() ?> diff --git a/composer.json b/composer.json index 06323749..0057dce1 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,9 @@ "codeigniter4/codeigniter4": "dev-develop", "league/commonmark": "^1.5", "vlucas/phpdotenv": "^5.2", - "league/html-to-markdown": "^4.10" + "league/html-to-markdown": "^4.10", + "podlibre/user-agents-php": "*", + "podlibre/ipcat": "*" }, "require-dev": { "mikey179/vfsstream": "1.6.*", @@ -27,8 +29,14 @@ }, "scripts": { "test": "phpunit", + "post-install-cmd": [ + "@php vendor/podlibre/user-agents-php/src/UserAgentsGenerate.php > vendor/podlibre/user-agents-php/src/UserAgents.php", + "@php vendor/podlibre/ipcat/IpDbGenerate.php > vendor/podlibre/ipcat/IpDb.php" + ], "post-update-cmd": [ - "@composer dump-autoload" + "@composer dump-autoload", + "@php vendor/podlibre/user-agents-php/src/UserAgentsGenerate.php > vendor/podlibre/user-agents-php/src/UserAgents.php", + "@php vendor/podlibre/ipcat/IpDbGenerate.php > vendor/podlibre/ipcat/IpDb.php" ] }, "support": { diff --git a/composer.lock b/composer.lock index 9e637f24..74c6baaf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "38eeae7f5d0143863430cda9df10d487", + "content-hash": "47b9f628f03f8c494a9339b054359ec8", "packages": [ { "name": "codeigniter4/codeigniter4", @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/codeigniter4/CodeIgniter4.git", - "reference": "9204aef421921f2c07021dda418ebfc200fe4a31" + "reference": "ccf68e1d7fc44bfe5abacc39bf16edae45794a83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/9204aef421921f2c07021dda418ebfc200fe4a31", - "reference": "9204aef421921f2c07021dda418ebfc200fe4a31", + "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/ccf68e1d7fc44bfe5abacc39bf16edae45794a83", + "reference": "ccf68e1d7fc44bfe5abacc39bf16edae45794a83", "shasum": "" }, "require": { @@ -37,6 +37,7 @@ "phpstan/phpstan": "^0.12", "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", + "rector/rector": "^0.8", "squizlabs/php_codesniffer": "^3.3" }, "type": "project", @@ -45,6 +46,11 @@ "CodeIgniter\\": "system/" } }, + "autoload-dev": { + "psr-4": { + "Utils\\": "utils" + } + }, "scripts": { "post-update-cmd": [ "@composer dump-autoload", @@ -69,7 +75,7 @@ "slack": "https://codeigniterchat.slack.com", "issues": "https://github.com/codeigniter4/CodeIgniter4/issues" }, - "time": "2020-09-24T17:15:24+00:00" + "time": "2020-10-04T20:15:33+00:00" }, { "name": "composer/ca-bundle", @@ -143,27 +149,27 @@ }, { "name": "geoip2/geoip2", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/maxmind/GeoIP2-php.git", - "reference": "419557cd21d9fe039721a83490701a58c8ce784a" + "reference": "d01be5894a5c1a3381c58c9b1795cd07f96c30f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/419557cd21d9fe039721a83490701a58c8ce784a", - "reference": "419557cd21d9fe039721a83490701a58c8ce784a", + "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/d01be5894a5c1a3381c58c9b1795cd07f96c30f7", + "reference": "d01be5894a5c1a3381c58c9b1795cd07f96c30f7", "shasum": "" }, "require": { "ext-json": "*", - "maxmind-db/reader": "~1.5", - "maxmind/web-service-common": "~0.6", - "php": ">=5.6" + "maxmind-db/reader": "~1.8", + "maxmind/web-service-common": "~0.8", + "php": ">=7.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "5.*", + "phpunit/phpunit": "^8.0 || ^9.0", "squizlabs/php_codesniffer": "3.*" }, "type": "library", @@ -192,7 +198,7 @@ "geolocation", "maxmind" ], - "time": "2019-12-12T18:48:39+00:00" + "time": "2020-10-01T18:48:34+00:00" }, { "name": "graham-campbell/result-type", @@ -689,23 +695,23 @@ }, { "name": "maxmind-db/reader", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "942553da239f12051275f9c666538b5dd09e2908" + "reference": "b566d429ac9aec10594b0935be8ff38302f8d5c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/942553da239f12051275f9c666538b5dd09e2908", - "reference": "942553da239f12051275f9c666538b5dd09e2908", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/b566d429ac9aec10594b0935be8ff38302f8d5c8", + "reference": "b566d429ac9aec10594b0935be8ff38302f8d5c8", "shasum": "" }, "require": { "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.7.0,>=2.0.0" + "ext-maxminddb": "<1.8.0,>=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "2.*", @@ -745,31 +751,31 @@ "geolocation", "maxmind" ], - "time": "2020-08-07T22:10:05+00:00" + "time": "2020-10-01T17:30:21+00:00" }, { "name": "maxmind/web-service-common", - "version": "v0.7.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/maxmind/web-service-common-php.git", - "reference": "74c996c218ada5c639c8c2f076756e059f5552fc" + "reference": "ba67d9532cfaf499bd71774b8170d05df4f75fb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/74c996c218ada5c639c8c2f076756e059f5552fc", - "reference": "74c996c218ada5c639c8c2f076756e059f5552fc", + "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/ba67d9532cfaf499bd71774b8170d05df4f75fb7", + "reference": "ba67d9532cfaf499bd71774b8170d05df4f75fb7", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0.3", "ext-curl": "*", "ext-json": "*", - "php": ">=5.6" + "php": ">=7.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0", "squizlabs/php_codesniffer": "3.*" }, "type": "library", @@ -791,7 +797,7 @@ ], "description": "Internal MaxMind Web Service API", "homepage": "https://github.com/maxmind/web-service-common-php", - "time": "2020-05-06T14:07:26+00:00" + "time": "2020-10-01T15:28:36+00:00" }, { "name": "myth/auth", @@ -918,6 +924,76 @@ ], "time": "2020-07-20T17:29:33+00:00" }, + { + "name": "podlibre/ipcat", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/podlibre/ipcat.git", + "reference": "1adfc821be508ddc8a742f6a5d5e6e42fdf28e86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/podlibre/ipcat/zipball/1adfc821be508ddc8a742f6a5d5e6e42fdf28e86", + "reference": "1adfc821be508ddc8a742f6a5d5e6e42fdf28e86", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Podlibre\\Ipcat\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-only" + ], + "authors": [ + { + "name": "Benjamin Bellamy", + "email": "ben@podlibre.org", + "homepage": "https://podlibre.org/" + } + ], + "description": "Categorization of IP Addresses forked from https://github.com/client9/ipcat", + "homepage": "https://github.com/podlibre/ipcat", + "time": "2020-10-05T17:15:07+00:00" + }, + { + "name": "podlibre/user-agents-php", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/podlibre/user-agents-php.git", + "reference": "891066bae6b4881a8b7a57eb72a67fca1fcf67c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/podlibre/user-agents-php/zipball/891066bae6b4881a8b7a57eb72a67fca1fcf67c0", + "reference": "891066bae6b4881a8b7a57eb72a67fca1fcf67c0", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Podlibre\\UserAgentsPhp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Bellamy", + "email": "ben@podlibre.org", + "homepage": "https://podlibre.org/" + } + ], + "description": "PHP implementation for opawg/user-agents.", + "homepage": "https://github.com/podlibre/user-agents-php", + "time": "2020-10-05T16:58:13+00:00" + }, { "name": "psr/cache", "version": "1.0.1", @@ -1801,28 +1877,28 @@ }, { "name": "phpspec/prophecy", - "version": "1.11.1", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160" + "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160", - "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d", + "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2", - "phpdocumentor/reflection-docblock": "^5.0", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { "phpspec/phpspec": "^6.0", - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^8.0 || ^9.0 <9.3" }, "type": "library", "extra": { @@ -1860,7 +1936,7 @@ "spy", "stub" ], - "time": "2020-07-08T12:44:21+00:00" + "time": "2020-09-29T09:10:42+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/docs/setup-development.md b/docs/setup-development.md index 66e02a3c..5949b1b6 100644 --- a/docs/setup-development.md +++ b/docs/setup-development.md @@ -104,6 +104,13 @@ docker ps -a docker-compose run --rm app php spark migrate -all ``` +In case you need to roll back, use this command: + +``` +# rolls back database schema loading (deletes all tables and their content) +docker-compose run --rm app php spark migrate:rollback +``` + 2. Populate the database with the required data: ```bash diff --git a/package-lock.json b/package-lock.json index ae21fc46..cb81a0b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,39 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@amcharts/amcharts4": { + "version": "4.10.5", + "resolved": "https://registry.npmjs.org/@amcharts/amcharts4/-/amcharts4-4.10.5.tgz", + "integrity": "sha512-H4PlVd4jSsD0V0loCg1Jd1gXMCqMaWwQabjd8qejP2lew2ZiXEaJ8eoIuXXoAakehCq/fewtgNO9lq95hdklvA==", + "requires": { + "@babel/runtime": "^7.6.3", + "core-js": "^3.0.0", + "d3-force": "^2.0.1", + "d3-geo": "^2.0.1", + "d3-geo-projection": "^3.0.0", + "pdfmake": "^0.1.36", + "polylabel": "^1.0.2", + "raf": "^3.4.1", + "regression": "^2.0.1", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "tslib": "^2.0.1", + "venn.js": "^0.2.20", + "xlsx": "^0.16.4" + }, + "dependencies": { + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + } + } + }, + "@amcharts/amcharts4-geodata": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@amcharts/amcharts4-geodata/-/amcharts4-geodata-4.1.17.tgz", + "integrity": "sha512-ylzshiOq/aMRlVrRq8dOznZP7fp4xg/XkmhTjGm2dN6O1WqvoVBBOOnGyzrmb2gBlqN5zj59PQyOqSDyQUNe/Q==" + }, "@babel/code-frame": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", @@ -1220,7 +1253,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" }, @@ -1228,8 +1260,7 @@ "regenerator-runtime": { "version": "0.13.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, @@ -2465,8 +2496,7 @@ "acorn": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", - "dev": true + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" }, "acorn-jsx": { "version": "5.3.1", @@ -2478,7 +2508,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, "requires": { "acorn": "^7.0.0", "acorn-walk": "^7.0.0", @@ -2488,8 +2517,16 @@ "acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "adler-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", + "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } }, "aggregate-error": { "version": "3.1.0", @@ -2513,12 +2550,42 @@ "uri-js": "^4.2.2" } }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + } + } + }, "alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -2582,6 +2649,11 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=" + }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", @@ -2612,6 +2684,58 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-transform": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz", + "integrity": "sha1-dJRAWIh9goPhidlUYAlHvJj+AGI=", + "requires": { + "escodegen": "~1.2.0", + "esprima": "~1.0.4", + "through": "~2.3.4" + }, + "dependencies": { + "escodegen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", + "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=", + "requires": { + "esprima": "~1.0.4", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.30" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=" + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "ast-types": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz", + "integrity": "sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk=" + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -2667,6 +2791,27 @@ "object.assign": "^4.1.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -2676,8 +2821,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -2734,6 +2878,11 @@ } } }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2756,7 +2905,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2791,6 +2939,68 @@ } } }, + "brfs": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", + "integrity": "sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==", + "requires": { + "quote-stream": "^1.0.1", + "resolve": "^1.1.5", + "static-module": "^3.0.2", + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "brotli": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", + "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=", + "requires": { + "base64-js": "^1.1.2" + }, + "dependencies": { + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + } + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } + } + }, + "browserify-optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz", + "integrity": "sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk=", + "requires": { + "ast-transform": "0.0.0", + "ast-types": "^0.7.0", + "browser-resolve": "^1.8.1" + } + }, "browserslist": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", @@ -2803,11 +3013,15 @@ "pkg-up": "^2.0.0" } }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "builtin-modules": { "version": "3.1.0", @@ -2915,6 +3129,25 @@ "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", "dev": true }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "cfb": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.0.tgz", + "integrity": "sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ==", + "requires": { + "adler-32": "~1.2.0", + "crc-32": "~1.2.0", + "printj": "~1.1.2" + } + }, "chalk": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", @@ -3239,6 +3472,11 @@ } } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, "clone-regexp": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", @@ -3280,6 +3518,22 @@ } } }, + "codepage": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", + "integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=", + "requires": { + "commander": "~2.14.1", + "exit-on-epipe": "~1.0.1" + }, + "dependencies": { + "commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" + } + } + }, "collapse-white-space": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", @@ -3444,8 +3698,18 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } }, "concat-with-sourcemaps": { "version": "1.1.0", @@ -3464,6 +3728,11 @@ } } }, + "contour_plot": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/contour_plot/-/contour_plot-0.0.1.tgz", + "integrity": "sha1-R1hw8DK44zhBKqX8UHiA8L9JXHc=" + }, "conventional-changelog-angular": { "version": "5.0.11", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", @@ -3510,7 +3779,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -3524,8 +3792,7 @@ "core-js": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, "core-js-compat": { "version": "3.6.5", @@ -3548,8 +3815,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -3563,6 +3829,15 @@ "parse-json": "^4.0.0" } }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, "crelt": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.4.tgz", @@ -3599,6 +3874,11 @@ } } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -3954,12 +4234,130 @@ } } }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "d3-array": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.8.0.tgz", + "integrity": "sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw==" + }, + "d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-dispatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", + "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==" + }, + "d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + }, + "d3-force": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", + "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "requires": { + "d3-dispatch": "1 - 2", + "d3-quadtree": "1 - 2", + "d3-timer": "1 - 2" + } + }, + "d3-geo": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.1.tgz", + "integrity": "sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A==", + "requires": { + "d3-array": ">=2.5" + } + }, + "d3-geo-projection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-3.0.0.tgz", + "integrity": "sha512-1JE+filVbkEX2bT25dJdQ05iA4QHvUwev6o0nIQHOSrNlHCAKfVss/U10vEM3pA4j5v7uQoFdQ4KLbx9BlEbWA==", + "requires": { + "commander": "2", + "d3-array": "1 - 2", + "d3-geo": "1.12.0 - 2", + "resolve": "^1.1.10" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } + } + }, + "d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "requires": { + "d3-color": "1" + } + }, + "d3-quadtree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", + "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + }, + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "d3-timer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", + "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + }, + "dependencies": { + "d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + } + } + }, "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -3972,8 +4370,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decamelize-keys": { "version": "1.1.0", @@ -4005,11 +4402,23 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", "dev": true }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deepmerge": { "version": "4.2.2", @@ -4020,7 +4429,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -4069,8 +4477,7 @@ "defined": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, "dependency-graph": { "version": "0.9.0", @@ -4101,6 +4508,11 @@ "minimist": "^1.1.1" } }, + "dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4177,6 +4589,22 @@ "is-obj": "^2.0.0" } }, + "dotignore": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", + "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + } + }, "electron-to-chromium": { "version": "1.3.459", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.459.tgz", @@ -4240,7 +4668,6 @@ "version": "1.17.6", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -4258,8 +4685,7 @@ "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" } } }, @@ -4267,13 +4693,77 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, "escalade": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", @@ -4283,8 +4773,62 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + } + } }, "eslint": { "version": "7.10.0", @@ -4484,8 +5028,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.3.1", @@ -4524,8 +5067,12 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "estree-is-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz", + "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==" }, "estree-walker": { "version": "1.0.1", @@ -4536,8 +5083,16 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } }, "eventemitter3": { "version": "4.0.7", @@ -4588,6 +5143,11 @@ "clone-regexp": "^2.1.0" } }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -4647,6 +5207,21 @@ "homedir-polyfill": "^1.0.1" } }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4830,8 +5405,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastest-levenshtein": { "version": "1.0.12", @@ -4965,12 +5539,129 @@ "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", "dev": true }, + "fmin": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/fmin/-/fmin-0.0.2.tgz", + "integrity": "sha1-Wbu0DUP/3ByUzQClaMQflfGXMBc=", + "requires": { + "contour_plot": "^0.0.1", + "json2module": "^0.0.3", + "rollup": "^0.25.8", + "tape": "^4.5.1", + "uglify-js": "^2.6.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "rollup": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.25.8.tgz", + "integrity": "sha1-v2zoO4dRDRY0Ru6qV37WpvxYNeA=", + "requires": { + "chalk": "^1.1.1", + "minimist": "^1.2.0", + "source-map-support": "^0.3.2" + } + }, + "source-map": { + "version": "0.1.32", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz", + "integrity": "sha1-yLbBZ3l7pHQKjqMyUhYv8IWRsmY=", + "requires": { + "amdefine": ">=0.0.4" + } + }, + "source-map-support": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.3.3.tgz", + "integrity": "sha1-NJAJd9W6PwfHdX7nLnO7GptTdU8=", + "requires": { + "source-map": "0.1.32" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "fontkit": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.1.tgz", + "integrity": "sha512-BsNCjDoYRxmNWFdAuK1y9bQt+igIxGtTC9u/jSFjR9MKhmI00rP1fwSvERt+5ddE82544l0XH5mzXozQVUy2Tw==", + "requires": { + "babel-runtime": "^6.26.0", + "brfs": "^2.0.0", + "brotli": "^1.2.0", + "browserify-optional": "^1.0.1", + "clone": "^1.0.4", + "deep-equal": "^1.0.0", + "dfa": "^1.2.0", + "restructure": "^0.5.3", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.2.2", + "unicode-trie": "^0.3.0" + }, + "dependencies": { + "unicode-trie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", + "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=", + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + } + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==" + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -4994,8 +5685,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -5007,8 +5697,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -5036,6 +5725,11 @@ "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", "dev": true }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5086,7 +5780,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5189,7 +5882,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -5198,7 +5890,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" }, @@ -5206,8 +5897,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" } } }, @@ -5220,8 +5910,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-value": { "version": "1.0.0", @@ -5471,7 +6160,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5480,8 +6168,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -5580,6 +6267,11 @@ "is-decimal": "^1.0.0" } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -5598,14 +6290,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" }, "is-color-stop": { "version": "1.1.0", @@ -5644,8 +6334,7 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-decimal": { "version": "1.0.4", @@ -5717,6 +6406,11 @@ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -5771,7 +6465,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -5807,7 +6500,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -5854,8 +6546,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -5942,6 +6633,14 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json2module": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/json2module/-/json2module-0.0.3.tgz", + "integrity": "sha1-APtfSpt638PwZHwpyxe80Zeb6bI=", + "requires": { + "rw": "^1.3.2" + } + }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -5978,6 +6677,11 @@ "integrity": "sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA==", "dev": true }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6003,6 +6707,16 @@ "type-check": "~0.4.0" } }, + "linebreak": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.0.2.tgz", + "integrity": "sha512-bJwSRsJeAmaZYnkcwl5sCQNfSDAhBuXxb6L27tb+qkBRtUQSSTUa5bcgCPD6hFEkRNlpWHfK7nFMmcANU7ZP1w==", + "requires": { + "base64-js": "0.0.8", + "brfs": "^2.0.2", + "unicode-trie": "^1.0.0" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -6665,6 +7379,14 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", + "requires": { + "source-map": "^0.5.6" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6720,7 +7442,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6728,8 +7449,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "4.1.0", @@ -6809,6 +7529,11 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -6933,11 +7658,69 @@ "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", "dev": true }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-is": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", + "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -6952,7 +7735,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -6995,7 +7777,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -7112,6 +7893,11 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7174,8 +7960,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -7186,8 +7971,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-type": { "version": "4.0.0", @@ -7195,6 +7979,44 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pdfkit": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.11.0.tgz", + "integrity": "sha512-1s9gaumXkYxcVF1iRtSmLiISF2r4nHtsTgpwXiK8Swe+xwk/1pm8FJjYqN7L3x13NsWnGyUFntWcO8vfqq+wwA==", + "requires": { + "crypto-js": "^3.1.9-1", + "fontkit": "^1.8.0", + "linebreak": "^1.0.2", + "png-js": "^1.0.0" + } + }, + "pdfmake": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.1.68.tgz", + "integrity": "sha512-oE1VEjkluro3+QqvLbFgFU/rRgyKdbPy/Fh8SS/nsUxnsiUcm85ChpmD6YD0hQW1E0d3hppAo4Yh+xdXucenIA==", + "requires": { + "iconv-lite": "^0.6.2", + "linebreak": "^1.0.2", + "pdfkit": "^0.11.0", + "svg-to-pdfkit": "^0.1.8", + "xmldoc": "^1.1.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "php-parser": { "version": "github:glayzzle/php-parser#5a0e2e1bf12517bd1c544c0f4e68482d0362a7b5", "from": "github:glayzzle/php-parser#5a0e2e1bf12517bd1c544c0f4e68482d0362a7b5", @@ -7290,6 +8112,19 @@ "semver-compare": "^1.0.0" } }, + "png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, + "polylabel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/polylabel/-/polylabel-1.1.0.tgz", + "integrity": "sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==", + "requires": { + "tinyqueue": "^2.0.3" + } + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -8798,11 +9633,15 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -9003,6 +9842,35 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "quote-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", + "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", + "requires": { + "buffer-equal": "0.0.1", + "minimist": "^1.1.3", + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9076,7 +9944,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -9173,6 +10040,15 @@ "safe-regex": "^1.1.0" } }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -9216,6 +10092,11 @@ } } }, + "regression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz", + "integrity": "sha1-jSnD6CJKEIUMNeM36FqLL6w7DIc=" + }, "remark": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz", @@ -9282,8 +10163,7 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "replace-ext": { "version": "1.0.0", @@ -9307,7 +10187,6 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -9353,6 +10232,22 @@ "signal-exit": "^3.0.2" } }, + "restructure": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-0.5.4.tgz", + "integrity": "sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg=", + "requires": { + "browserify-optional": "^1.0.0" + } + }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "requires": { + "through": "~2.3.4" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -9377,6 +10272,19 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, + "rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=" + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "^0.1.1" + } + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -9582,6 +10490,11 @@ "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", "dev": true }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rxjs": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", @@ -9594,8 +10507,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-identifier": { "version": "0.4.2", @@ -9615,14 +10527,26 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scope-analyzer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.1.1.tgz", + "integrity": "sha512-azEAihtQ9mEyZGhfgTJy3IbOWEzeOrYbg7NcYEshPKnKd+LZmC3TNd5dmDxbLBsTG/JVWmCp+vDJ03vJjeXMHg==", + "requires": { + "array-from": "^2.1.1", + "dash-ast": "^1.0.0", + "es6-map": "^0.1.5", + "es6-set": "^0.1.5", + "es6-symbol": "^3.1.1", + "estree-is-function": "^1.0.0", + "get-assigned-identifiers": "^1.1.0" + } }, "semver": { "version": "5.7.1", @@ -9680,6 +10604,11 @@ } } }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9852,8 +10781,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.3", @@ -9895,8 +10823,7 @@ "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "spdx-correct": { "version": "3.1.1", @@ -9971,18 +10898,39 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "requires": { + "frac": "~1.1.2" + } + }, "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stackblur-canvas": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.4.0.tgz", + "integrity": "sha512-Z+HixfgYV0ss3C342DxPwc+UvN1SYWqoz7Wsi3xEDWEnaBkSCL3Ey21gF4io+WlLm8/RIrSnCrDBIEcH4O+q5Q==" + }, "state-toggle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", "dev": true }, + "static-eval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", + "integrity": "sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==", + "requires": { + "escodegen": "^1.11.1" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -10004,6 +10952,46 @@ } } }, + "static-module": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.4.tgz", + "integrity": "sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw==", + "requires": { + "acorn-node": "^1.3.0", + "concat-stream": "~1.6.0", + "convert-source-map": "^1.5.1", + "duplexer2": "~0.1.4", + "escodegen": "^1.11.1", + "has": "^1.0.1", + "magic-string": "0.25.1", + "merge-source-map": "1.0.4", + "object-inspect": "^1.6.0", + "readable-stream": "~2.3.3", + "scope-analyzer": "^2.0.1", + "shallow-copy": "~0.0.1", + "static-eval": "^2.0.5", + "through2": "~2.0.3" + }, + "dependencies": { + "magic-string": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", + "requires": { + "sourcemap-codec": "^1.4.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -10037,11 +11025,64 @@ } } }, + "string.prototype.trim": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.2.tgz", + "integrity": "sha512-b5yrbl3BXIjHau9Prk7U0RRYcUYdN4wGSVaqoBQS50CCE3KBuYU0TYRNPFCP7aVoNMX87HKThdMRVIP3giclKg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -10051,7 +11092,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -10061,7 +11101,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -10548,6 +11587,14 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, + "svg-to-pdfkit": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/svg-to-pdfkit/-/svg-to-pdfkit-0.1.8.tgz", + "integrity": "sha512-QItiGZBy5TstGy+q8mjQTMGRlDDOARXLxH+sgVm1n/LYeo0zFcQlcCh8m4zi8QxctrxB9Kue/lStc/RD5iLadQ==", + "requires": { + "pdfkit": ">=0.8.1" + } + }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -10648,6 +11695,43 @@ "resolve": "^1.14.2" } }, + "tape": { + "version": "4.13.3", + "resolved": "https://registry.npmjs.org/tape/-/tape-4.13.3.tgz", + "integrity": "sha512-0/Y20PwRIUkQcTCSi4AASs+OANZZwqPKaipGCEwp10dQMipVvSZwUUCi01Y/OklIGyHKFhIcjock+DKnBfLAFw==", + "requires": { + "deep-equal": "~1.1.1", + "defined": "~1.0.0", + "dotignore": "~0.1.2", + "for-each": "~0.3.3", + "function-bind": "~1.1.1", + "glob": "~7.1.6", + "has": "~1.0.3", + "inherits": "~2.0.4", + "is-regex": "~1.0.5", + "minimist": "~1.2.5", + "object-inspect": "~1.7.0", + "resolve": "~1.17.0", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.2.1", + "through": "~2.3.8" + }, + "dependencies": { + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + } + } + }, "terser": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.2.tgz", @@ -10688,8 +11772,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "3.0.2", @@ -10707,6 +11790,16 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, + "tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -10815,6 +11908,11 @@ "tslib": "^1.8.1" } }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10830,6 +11928,11 @@ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -10850,6 +11953,50 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, "unherit": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", @@ -10882,12 +12029,46 @@ "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", "dev": true }, + "unicode-properties": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.3.1.tgz", + "integrity": "sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA==", + "requires": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + }, + "dependencies": { + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + } + } + }, "unicode-property-aliases-ecmascript": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", "dev": true }, + "unicode-trie": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-1.0.0.tgz", + "integrity": "sha512-v5raLKsobbFbWLMoX9+bChts/VhPPj3XpkNr/HbqkirXR1DPk8eo9IYKyvk0MQZFkaoRsFj2Rmaqgi2rfAZYtA==", + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "unified": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", @@ -11070,8 +12251,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.1", @@ -11107,6 +12287,16 @@ "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", "dev": true }, + "venn.js": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/venn.js/-/venn.js-0.2.20.tgz", + "integrity": "sha512-bb5SYq/wamY9fvcuErb9a0FJkgIFHJjkLZWonQ+DoKKuDX3WPH2B4ouI1ce4K2iejBklQy6r1ly8nOGIyOCO6w==", + "requires": { + "d3-selection": "^1.0.2", + "d3-transition": "^1.0.1", + "fmin": "0.0.2" + } + }, "vfile": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz", @@ -11170,11 +12360,30 @@ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", "dev": true }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==" + }, + "word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==" + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" }, "wrap-ansi": { "version": "6.2.0", @@ -11249,8 +12458,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -11273,11 +12481,41 @@ "typedarray-to-buffer": "^3.1.5" } }, + "xlsx": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.16.7.tgz", + "integrity": "sha512-Xc4NRjci2Grbh9NDk/XoaWycJurxEug1wwn0aJCmB0NvIMyQuHYq2muWLWGidYNZPf94aUbqm6K8Fbjd7gKTZg==", + "requires": { + "adler-32": "~1.2.0", + "cfb": "^1.1.4", + "codepage": "~1.14.0", + "commander": "~2.17.1", + "crc-32": "~1.2.0", + "exit-on-epipe": "~1.0.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + } + } + }, + "xmldoc": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.1.2.tgz", + "integrity": "sha512-ruPC/fyPNck2BD1dpz0AZZyrEwMOrWTO5lDdIXS91rs3wtm4j+T8Rp2o+zoOYkkAxJTZRPOSnOGei1egoRmKMQ==", + "requires": { + "sax": "^1.2.1" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", diff --git a/package.json b/package.json index a131f985..4adcadf9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "commit": "git-cz" }, "dependencies": { + "@amcharts/amcharts4": "^4.9.37", + "@amcharts/amcharts4-geodata": "^4.1.17", "@popperjs/core": "^2.5.3", "choices.js": "^9.0.1", "prosemirror-example-setup": "^1.1.2", diff --git a/public/favicon.ico b/public/favicon.ico index 3a7011d3..a55e7ac7 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ