From 7202b9867bd59aafa8c338a4230fb5e5c55b24c6 Mon Sep 17 00:00:00 2001 From: Benjamin Bellamy <ben@podlibre.org> Date: Wed, 21 Oct 2020 16:04:18 +0000 Subject: [PATCH] feat(analytics): add service name from rss user-agent BREAKING CHANGE: analytics_podcasts_by_player table and analytics_podcasts procedure were updated --- app/Controllers/Analytics.php | 10 +- app/Controllers/Feed.php | 27 ++++- ...40000_add_analytics_podcasts_by_player.php | 5 + ...dd_analytics_podcasts_stored_procedure.php | 5 +- app/Helpers/analytics_helper.php | 29 ++--- app/Helpers/rss_helper.php | 5 +- app/Language/en/Charts.php | 19 ++-- app/Language/fr/Charts.php | 20 ++-- app/Models/AnalyticsPodcastByPlayerModel.php | 35 ++++++ app/Models/EpisodeModel.php | 7 +- app/Models/PodcastModel.php | 5 +- app/Views/admin/podcast/analytics/players.php | 7 +- app/Views/admin/podcast/create.php | 20 +++- composer.json | 8 +- composer.lock | 103 +++++++++--------- 15 files changed, 200 insertions(+), 105 deletions(-) diff --git a/app/Controllers/Analytics.php b/app/Controllers/Analytics.php index 3482b1eb69..7066c5e340 100644 --- a/app/Controllers/Analytics.php +++ b/app/Controllers/Analytics.php @@ -55,7 +55,15 @@ class Analytics extends Controller ) { helper('media'); - podcast_hit($podcastId, $episodeId, $bytesThreshold, $fileSize); + $serviceName = isset($_GET['s']) ? $_GET['s'] : ''; + + podcast_hit( + $podcastId, + $episodeId, + $bytesThreshold, + $fileSize, + $serviceName + ); return redirect()->to(media_url(implode('/', $filename))); } } diff --git a/app/Controllers/Feed.php b/app/Controllers/Feed.php index a739a793b1..3da87f0dd3 100644 --- a/app/Controllers/Feed.php +++ b/app/Controllers/Feed.php @@ -15,13 +15,32 @@ class Feed extends Controller { public function index($podcastName) { - // The page cache is set to a decade so it is deleted manually upon podcast update - $this->cachePage(DECADE); - helper('rss'); $podcast = (new PodcastModel())->where('name', $podcastName)->first(); + if (!$podcast) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); + } - return $this->response->setXML(get_rss_feed($podcast)); + $service = null; + try { + $service = \Opawg\UserAgentsPhp\UserAgentsRSS::find( + $_SERVER['HTTP_USER_AGENT'] + ); + } catch (\Exception $e) { + // If things go wrong the show must go on and the user must be able to download the file + log_message('critical', $e); + } + $cacheName = + "podcast{$podcast->id}_feed" . + ($service ? "_{$service['slug']}" : ''); + if (!($found = cache($cacheName))) { + $found = get_rss_feed( + $podcast, + $service ? '?s=' . urlencode($service['name']) : '' + ); + cache()->save($cacheName, $found, DECADE); + } + return $this->response->setXML($found); } } diff --git a/app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php b/app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php index a8f8d32d7d..192ebd07fb 100644 --- a/app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php +++ b/app/Database/Migrations/2020-06-08-140000_add_analytics_podcasts_by_player.php @@ -25,6 +25,10 @@ class AddAnalyticsPodcastsByPlayer extends Migration 'date' => [ 'type' => 'date', ], + 'service' => [ + 'type' => 'VARCHAR', + 'constraint' => 128, + ], 'app' => [ 'type' => 'VARCHAR', 'constraint' => 128, @@ -51,6 +55,7 @@ class AddAnalyticsPodcastsByPlayer extends Migration $this->forge->addPrimaryKey([ 'podcast_id', 'date', + 'service', 'app', 'device', 'os', 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 7089e51e62..9978a59f0b 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 @@ -28,6 +28,7 @@ CREATE PROCEDURE `{$prefix}analytics_podcasts` ( IN `p_region_code` VARCHAR(3) CHARSET utf8mb4, IN `p_latitude` FLOAT, IN `p_longitude` FLOAT, + IN `p_service` VARCHAR(128) CHARSET utf8mb4, IN `p_app` VARCHAR(128) CHARSET utf8mb4, IN `p_device` VARCHAR(32) CHARSET utf8mb4, IN `p_os` VARCHAR(32) CHARSET utf8mb4, @@ -52,8 +53,8 @@ IF NOT `p_bot` THEN VALUES (p_podcast_id, p_country_code, p_region_code, p_latitude, p_longitude, DATE(NOW())) ON DUPLICATE KEY UPDATE `hits`=`hits`+1; END IF; -INSERT INTO `{$prefix}analytics_podcasts_by_player`(`podcast_id`, `app`, `device`, `os`, `bot`, `date`) - VALUES (p_podcast_id, p_app, p_device, p_os, p_bot, DATE(NOW())) +INSERT INTO `{$prefix}analytics_podcasts_by_player`(`podcast_id`, `service`, `app`, `device`, `os`, `bot`, `date`) + VALUES (p_podcast_id, p_service, p_app, p_device, p_os, p_bot, DATE(NOW())) ON DUPLICATE KEY UPDATE `hits`=`hits`+1; END EOD; diff --git a/app/Helpers/analytics_helper.php b/app/Helpers/analytics_helper.php index 5c242d27c1..1eef6ecce3 100644 --- a/app/Helpers/analytics_helper.php +++ b/app/Helpers/analytics_helper.php @@ -102,7 +102,7 @@ function set_user_session_player() $userAgent = $_SERVER['HTTP_USER_AGENT']; try { - $playerFound = \Podlibre\UserAgentsPhp\UserAgents::find($userAgent); + $playerFound = \Opawg\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 } @@ -227,6 +227,7 @@ function webpage_hit($podcast_id) * ✅ Castopod does not do pre-load * ✅ IP deny list https://github.com/client9/ipcat * ✅ User-agent Filtering https://github.com/opawg/user-agents + * ✅ RSS User-agent https://github.com/opawg/podcast-rss-useragents * ✅ 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 @@ -234,11 +235,17 @@ function webpage_hit($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 + * @param string $serviceName The name of the service that had fetched the RSS feed * * @return void */ -function podcast_hit($podcastId, $episodeId, $bytesThreshold, $fileSize) -{ +function podcast_hit( + $podcastId, + $episodeId, + $bytesThreshold, + $fileSize, + $serviceName +) { $session = \Config\Services::session(); $session->start(); @@ -328,22 +335,18 @@ function podcast_hit($podcastId, $episodeId, $bytesThreshold, $fileSize) // We save the download count for this user until midnight: cache()->save($listenerHashId, $downloadsByUser, $midnightTTL); - $app = $session->get('player')['app']; - $device = $session->get('player')['device']; - $os = $session->get('player')['os']; - $bot = $session->get('player')['bot']; - - $db->query("CALL $procedureName(?,?,?,?,?,?,?,?,?,?,?);", [ + $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, + $serviceName, + $session->get('player')['app'], + $session->get('player')['device'], + $session->get('player')['os'], + $session->get('player')['bot'], $newListener, ]); } diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 7841337834..9627c3d128 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -13,9 +13,10 @@ use CodeIgniter\I18n\Time; * Generates the rss feed for a given podcast entity * * @param App\Entities\Podcast $podcast + * @param string $service The name of the service that fetches the RSS feed for future reference when the audio file is eventually downloaded * @return string rss feed as xml */ -function get_rss_feed($podcast) +function get_rss_feed($podcast, $serviceName = '') { $episodes = $podcast->episodes; @@ -102,7 +103,7 @@ function get_rss_feed($podcast) $item->addChild('title', $episode->title); $enclosure = $item->addChild('enclosure'); - $enclosure->addAttribute('url', $episode->enclosure_url); + $enclosure->addAttribute('url', $episode->enclosure_url . $serviceName); $enclosure->addAttribute('length', $episode->enclosure_filesize); $enclosure->addAttribute('type', $episode->enclosure_mimetype); diff --git a/app/Language/en/Charts.php b/app/Language/en/Charts.php index ac27218ebe..206df55635 100644 --- a/app/Language/en/Charts.php +++ b/app/Language/en/Charts.php @@ -7,22 +7,23 @@ */ return [ - 'by_player_weekly' => 'Podcast downloads by player (for the past week)', - 'by_player_yearly' => 'Podcast downloads by player (for the past year)', - 'by_device_weekly' => 'Podcast downloads by device (for the past week)', - 'by_os_weekly' => 'Podcast downloads by O.S. (for the past week)', - 'podcast_by_region' => 'Podcast downloads by region (for the past week)', + 'by_service_weekly' => 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', 'unique_daily_listeners' => 'Daily unique listeners', 'unique_monthly_listeners' => 'Monthly unique listeners', 'by_browser' => 'Web pages usage by browser (for the past week)', - 'podcast_by_day' => 'Podcast daily downloads', - 'podcast_by_month' => 'Podcast monthly downloads', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', 'episode_by_day' => 'Episode daily downloads (first 60 days)', 'episode_by_month' => 'Episode monthly downloads', 'episodes_by_day' => '5 latest episodes downloads (during their first 60 days)', - 'by_country_weekly' => 'Podcast downloads by country (for the past week)', - 'by_country_yearly' => 'Podcast downloads by country (for the past year)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', 'by_domain_weekly' => 'Web pages visits by source (for the past week)', 'by_domain_yearly' => 'Web pages visits by source (for the past year)', 'by_entry_page' => 'Web pages visits by landing page (for the past week)', diff --git a/app/Language/fr/Charts.php b/app/Language/fr/Charts.php index b878234d10..14607d2690 100644 --- a/app/Language/fr/Charts.php +++ b/app/Language/fr/Charts.php @@ -7,31 +7,33 @@ */ return [ + 'by_service_weekly' => + 'Téléchargements d’épisodes par service (sur la dernière semaine)', 'by_player_weekly' => - 'Téléchargements de Podcast par lecteur (sur la dernière semaine)', + 'Téléchargements d’épisodes par lecteur (sur la dernière semaine)', 'by_player_yearly' => - 'Téléchargements de Podcast par lecteur (sur la dernière année)', + 'Téléchargements d’épisodes par lecteur (sur la dernière année)', 'by_device_weekly' => - 'Téléchargements de Podcast par appareil (sur la dernière semaine)', + 'Téléchargements d’épisodes par appareil (sur la dernière semaine)', 'by_os_weekly' => - 'Téléchargements de Podcast par OS (sur la dernière semaine)', + 'Téléchargements d’épisodes par OS (sur la dernière semaine)', 'podcast_by_region' => - 'Téléchargements de Podcast par région (sur la dernière semaine)', + 'Téléchargements d’épisodes par région (sur la dernière semaine)', 'unique_daily_listeners' => 'Auditeurs uniques quotidiens', 'unique_monthly_listeners' => 'Auditeurs uniques mensuels', 'by_browser' => 'Fréquentation des pages web par navigateur (sur la dernière semaine)', - 'podcast_by_day' => 'Téléchargements quotidiens de podcasts', - 'podcast_by_month' => 'Téléchargements mensuels de podcasts', + 'podcast_by_day' => 'Téléchargements quotidiens d’épisodes', + 'podcast_by_month' => 'Téléchargements mensuels d’épisodes', 'episode_by_day' => 'Téléchargements quotidiens de l’épisode (sur les 60 premiers jours)', 'episode_by_month' => 'Téléchargements mensuels de l’épisode', 'episodes_by_day' => 'Téléchargements des 5 derniers épisodes (sur les 60 premiers jours)', 'by_country_weekly' => - 'Téléchargement de podcasts par pays (sur la dernière semaine)', + 'Téléchargement d’épisodes par pays (sur la dernière semaine)', 'by_country_yearly' => - 'Téléchargement de podcasts par pays (sur la dernière année)', + 'Téléchargement d’épisodes par pays (sur la dernière année)', 'by_domain_weekly' => 'Fréquentation des pages web par origine (sur la dernière semaine)', 'by_domain_yearly' => diff --git a/app/Models/AnalyticsPodcastByPlayerModel.php b/app/Models/AnalyticsPodcastByPlayerModel.php index bb03fcf81e..1fb6b899b2 100644 --- a/app/Models/AnalyticsPodcastByPlayerModel.php +++ b/app/Models/AnalyticsPodcastByPlayerModel.php @@ -23,6 +23,41 @@ class AnalyticsPodcastByPlayerModel extends Model protected $useTimestamps = false; + /** + * Gets service data for a podcast + * + * @param int $podcastId + * + * @return array + */ + public function getDataByServiceWeekly(int $podcastId): array + { + if ( + !($found = cache( + "{$podcastId}_analytics_podcasts_by_player_by_service_weekly" + )) + ) { + $found = $this->select('`service` as `labels`') + ->selectSum('`hits`', '`values`') + ->where([ + '`podcast_id`' => $podcastId, + '`service` !=' => '', + '`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_service_weekly", + $found, + 600 + ); + } + return $found; + } + /** * Gets player data for a podcast * diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index eb1e1f990d..2242266c26 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -272,7 +272,12 @@ class EpisodeModel extends Model ); // delete cache for rss feed - cache()->delete(md5($episode->podcast->feed_url)); + cache()->delete("podcast{$episode->podcast_id}_feed"); + foreach (\Opawg\UserAgentsPhp\UserAgentsRSS::$db as $service) { + cache()->delete( + "podcast{$episode->podcast_id}_feed_{$service['slug']}" + ); + } // delete model requests cache cache()->delete("podcast{$episode->podcast_id}_episodes"); diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index bb92d0e926..5c75640eba 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -173,7 +173,10 @@ class PodcastModel extends Model $supportedLocales = config('App')->supportedLocales; // delete cache for rss feed and podcast pages - cache()->delete(md5($podcast->feed_url)); + cache()->delete("podcast{$podcast->id}_feed"); + foreach (\Opawg\UserAgentsPhp\UserAgentsRSS::$db as $service) { + cache()->delete("podcast{$podcast->id}_feed_{$service['slug']}"); + } // delete model requests cache cache()->delete("podcast{$podcast->id}"); diff --git a/app/Views/admin/podcast/analytics/players.php b/app/Views/admin/podcast/analytics/players.php index 2a5fa59dcd..fcc2e83bf7 100644 --- a/app/Views/admin/podcast/analytics/players.php +++ b/app/Views/admin/podcast/analytics/players.php @@ -21,14 +21,13 @@ ) ?>"></div> </div> - <div class="mb-12 mr-6 text-center"> - <h2><?= lang('Charts.by_player_yearly') ?></h2> - <div class="chart-pie" id="by-app-yearly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to( + <h2><?= lang('Charts.by_service_weekly') ?></h2> + <div class="chart-pie" id="by-service-weekly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to( 'analytics-data', $podcast->id, 'PodcastByPlayer', - 'ByAppYearly' + 'ByServiceWeekly' ) ?>"></div> </div> diff --git a/app/Views/admin/podcast/create.php b/app/Views/admin/podcast/create.php index 1ee8414124..7b08bd8bf1 100644 --- a/app/Views/admin/podcast/create.php +++ b/app/Views/admin/podcast/create.php @@ -27,6 +27,7 @@ 'id' => 'image', 'name' => 'image', 'class' => 'form-input', + 'required' => 'required', 'type' => 'file', 'accept' => '.jpg,.jpeg,.png', @@ -58,21 +59,27 @@ 'required' => 'required', ]) ?> -<?= form_fieldset('', [ - 'class' => 'mb-4', -]) ?> +<?= form_fieldset('', ['class' => 'mb-4']) ?> <legend> <?= lang('Podcast.form.type.label') . hint_tooltip(lang('Podcast.form.type.hint'), 'ml-1') ?> </legend> <?= form_radio( - ['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio-btn'], + [ + 'id' => 'episodic', + 'name' => 'type', + 'class' => 'form-radio-btn', + ], 'episodic', old('type') ? old('type') == 'episodic' : true ) ?> <label for="episodic"><?= lang('Podcast.form.type.episodic') ?></label> <?= form_radio( - ['id' => 'serial', 'name' => 'type', 'class' => 'form-radio-btn'], + [ + 'id' => 'serial', + 'name' => 'type', + 'class' => 'form-radio-btn', + ], 'serial', old('type') ? old('type') == 'serial' : false ) ?> @@ -252,6 +259,7 @@ ['id' => 'block', 'name' => 'block'], 'yes', old('block', false), + 'mb-2' ) ?> @@ -266,7 +274,7 @@ lang('Podcast.form.lock'), ['id' => 'lock', 'name' => 'lock'], 'yes', - old('lock', $podcast->lock) + old('lock', true) ) ?> <?= form_section_close() ?> diff --git a/composer.json b/composer.json index 214a266401..b2bce3e852 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "league/commonmark": "^1.5", "vlucas/phpdotenv": "^5.2", "league/html-to-markdown": "^4.10", - "podlibre/user-agents-php": "*", + "opawg/user-agents-php": "*", "podlibre/ipcat": "*" }, "require-dev": { @@ -31,12 +31,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/opawg/user-agents-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-php/src/UserAgents.php", + "@php vendor/opawg/user-agents-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-php/src/UserAgentsRSS.php", "@php vendor/podlibre/ipcat/IpDbGenerate.php > vendor/podlibre/ipcat/IpDb.php" ], "post-update-cmd": [ "@composer dump-autoload", - "@php vendor/podlibre/user-agents-php/src/UserAgentsGenerate.php > vendor/podlibre/user-agents-php/src/UserAgents.php", + "@php vendor/opawg/user-agents-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-php/src/UserAgents.php", + "@php vendor/opawg/user-agents-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-php/src/UserAgentsRSS.php", "@php vendor/podlibre/ipcat/IpDbGenerate.php > vendor/podlibre/ipcat/IpDb.php" ] }, diff --git a/composer.lock b/composer.lock index 27374187ff..38bacea659 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": "47b9f628f03f8c494a9339b054359ec8", + "content-hash": "37551523e4097a9341bc00dd317f573d", "packages": [ { "name": "codeigniter4/codeigniter4", @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/codeigniter4/CodeIgniter4.git", - "reference": "f5545aa7274575c397efae4ebcf6c18779dcf895" + "reference": "13ff147fa4cd9db15888b041ef35bc22ed94252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/f5545aa7274575c397efae4ebcf6c18779dcf895", - "reference": "f5545aa7274575c397efae4ebcf6c18779dcf895", + "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/13ff147fa4cd9db15888b041ef35bc22ed94252a", + "reference": "13ff147fa4cd9db15888b041ef35bc22ed94252a", "shasum": "" }, "require": { @@ -75,7 +75,7 @@ "slack": "https://codeigniterchat.slack.com", "issues": "https://github.com/codeigniter4/CodeIgniter4/issues" }, - "time": "2020-10-06T06:38:58+00:00" + "time": "2020-10-20T18:13:11+00:00" }, { "name": "composer/ca-bundle", @@ -518,16 +518,16 @@ }, { "name": "league/commonmark", - "version": "1.5.5", + "version": "1.5.6", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "45832dfed6007b984c0d40addfac48d403dc6432" + "reference": "a56e91e0fa1f6d0049153a9c34f63488f6b7ce61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/45832dfed6007b984c0d40addfac48d403dc6432", - "reference": "45832dfed6007b984c0d40addfac48d403dc6432", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a56e91e0fa1f6d0049153a9c34f63488f6b7ce61", + "reference": "a56e91e0fa1f6d0049153a9c34f63488f6b7ce61", "shasum": "" }, "require": { @@ -609,7 +609,7 @@ "type": "tidelift" } ], - "time": "2020-09-13T14:44:46+00:00" + "time": "2020-10-17T21:33:03+00:00" }, { "name": "league/html-to-markdown", @@ -805,12 +805,12 @@ "source": { "type": "git", "url": "https://github.com/lonnieezell/myth-auth.git", - "reference": "e838cb8de6ffa118caf2b9909e71776a866c8973" + "reference": "e9d6a2f557bd275158e0b84624534b2abeeb539c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lonnieezell/myth-auth/zipball/e838cb8de6ffa118caf2b9909e71776a866c8973", - "reference": "e838cb8de6ffa118caf2b9909e71776a866c8973", + "url": "https://api.github.com/repos/lonnieezell/myth-auth/zipball/e9d6a2f557bd275158e0b84624534b2abeeb539c", + "reference": "e9d6a2f557bd275158e0b84624534b2abeeb539c", "shasum": "" }, "require": { @@ -818,9 +818,12 @@ }, "require-dev": { "codeigniter4/codeigniter4": "dev-develop", + "codeigniter4/codeigniter4-standard": "^1.0", "fzaninotto/faker": "^1.9@dev", "mockery/mockery": "^1.0", - "phpunit/phpunit": "8.5.*" + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5", + "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "autoload": { @@ -857,7 +860,42 @@ "type": "patreon" } ], - "time": "2020-09-07T03:37:26+00:00" + "time": "2020-10-16T18:51:37+00:00" + }, + { + "name": "opawg/user-agents-php", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/opawg/user-agents-php.git", + "reference": "3b71eeed2c3216f1c1c361c62d4d3a7002be0481" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opawg/user-agents-php/zipball/3b71eeed2c3216f1c1c361c62d4d3a7002be0481", + "reference": "3b71eeed2c3216f1c1c361c62d4d3a7002be0481", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Opawg\\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/opawg/user-agents-php", + "time": "2020-10-20T23:22:20+00:00" }, { "name": "phpoption/phpoption", @@ -959,41 +997,6 @@ "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", -- GitLab