Commit 77ccb306 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

fix(analytics): set EpisodeAudioController to init user session data

parent 998a8ee6
Loading
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -195,10 +195,10 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
});

// audio routes
$routes->head('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeController::audio/$1/$2', [
$routes->head('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioController/$1/$2', [
    'as' => 'episode-audio',
], );
$routes->get('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeController::audio/$1/$2', [
$routes->get('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioController/$1/$2', [
    'as' => 'episode-audio',
], );

+163 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * @copyright  2020 Ad Aures
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Controllers;

use App\Entities\Episode;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\Controller;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Modules\Analytics\Config\Analytics;
use Modules\PremiumPodcasts\Models\SubscriptionModel;
use Psr\Log\LoggerInterface;

class EpisodeAudioController extends Controller
{
    /**
     * An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
     * other controllers that extend Analytics.
     *
     * @var string[]
     */
    protected $helpers = ['analytics'];

    protected Podcast $podcast;

    protected Episode $episode;

    protected Analytics $analyticsConfig;

    /**
     * Constructor.
     */
    public function initController(
        RequestInterface $request,
        ResponseInterface $response,
        LoggerInterface $logger
    ): void {
        // Do Not Edit This Line
        parent::initController($request, $response, $logger);

        set_user_session_deny_list_ip();
        set_user_session_location();
        set_user_session_player();

        $this->analyticsConfig = config('Analytics');
    }

    public function _remap(string $method, string ...$params): mixed
    {
        if (count($params) < 2) {
            throw PageNotFoundException::forPageNotFound();
        }

        if (
            ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
        ) {
            throw PageNotFoundException::forPageNotFound();
        }

        $this->podcast = $podcast;

        if (
            ($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null
            ) {
            throw PageNotFoundException::forPageNotFound();
        }

        $this->episode = $episode;

        unset($params[1]);
        unset($params[0]);

        return $this->{$method}(...$params);
    }

    public function index(): RedirectResponse | ResponseInterface
    {
        // check if episode is premium?
        $subscription = null;

        // check if podcast is already unlocked before any token validation
        if ($this->episode->is_premium && ($subscription = service('premium_podcasts')->subscription(
            $this->episode->podcast->handle
        )) === null) {
            // look for token as GET parameter
            if (($token = $this->request->getGet('token')) === null) {
                return $this->response->setStatusCode(401)
                    ->setJSON([
                        'errors' => [
                            'status' => 401,
                            'title' => 'Unauthorized',
                            'detail' => 'Episode is premium, you must provide a token to unlock it.',
                        ],
                    ]);
            }

            // check if there's a valid subscription for the provided token
            if (($subscription = (new SubscriptionModel())->validateSubscription(
                $this->episode->podcast->handle,
                $token
            )) === null) {
                return $this->response->setStatusCode(401, 'Invalid token!')
                    ->setJSON([
                        'errors' => [
                            'status' => 401,
                            'title' => 'Unauthorized',
                            'detail' => 'Invalid token!',
                        ],
                    ]);
            }
        }

        $session = Services::session();
        $session->start();

        $serviceName = '';
        if ($this->request->getGet('_from')) {
            $serviceName = $this->request->getGet('_from');
        } elseif ($session->get('embed_domain') !== null) {
            $serviceName = $session->get('embed_domain');
        } elseif ($session->get('referer') !== null && $session->get('referer') !== '- Direct -') {
            $serviceName = parse_url((string) $session->get('referer'), PHP_URL_HOST);
        }

        $audioFileSize = $this->episode->audio->file_size;
        $audioFileHeaderSize = $this->episode->audio->header_size;
        $audioDuration = $this->episode->audio->duration;

        // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics
        // - if audio is less than or equal to 60s, then take the audio file_size
        // - if audio is more than 60s, then take the audio file_header_size + 60s
        $bytesThreshold = $audioDuration <= 60
        ? $audioFileSize
        : $audioFileHeaderSize +
        (int) floor((($audioFileSize - $audioFileHeaderSize) / $audioDuration) * 60);

        podcast_hit(
            $this->episode->podcast_id,
            $this->episode->id,
            $bytesThreshold,
            $audioFileSize,
            $audioDuration,
            $this->episode->published_at->getTimestamp(),
            $serviceName,
            $subscription !== null ? $subscription->id : null
        );

        return redirect()->to($this->analyticsConfig->getAudioUrl($this->episode, $this->request->getGet()));
    }
}
+0 −80
Original line number Diff line number Diff line
@@ -19,14 +19,12 @@ use App\Models\PodcastModel;
use App\Models\PostModel;
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Modules\Analytics\AnalyticsTrait;
use Modules\Fediverse\Objects\OrderedCollectionObject;
use Modules\Fediverse\Objects\OrderedCollectionPage;
use Modules\PremiumPodcasts\Models\SubscriptionModel;
use SimpleXMLElement;

class EpisodeController extends BaseController
@@ -331,82 +329,4 @@ class EpisodeController extends BaseController
            ->setHeader('Access-Control-Allow-Origin', '*')
            ->setBody($collection->toJSON());
    }

    public function audio(): RedirectResponse | ResponseInterface
    {
        // check if episode is premium?
        $subscription = null;

        // check if podcast is already unlocked before any token validation
        if ($this->episode->is_premium && ($subscription = service('premium_podcasts')->subscription(
            $this->episode->podcast->handle
        )) === null) {
            // look for token as GET parameter
            if (($token = $this->request->getGet('token')) === null) {
                return $this->response->setStatusCode(401)
                    ->setJSON([
                        'errors' => [
                            'status' => 401,
                            'title' => 'Unauthorized',
                            'detail' => 'Episode is premium, you must provide a token to unlock it.',
                        ],
                    ]);
            }

            // check if there's a valid subscription for the provided token
            if (($subscription = (new SubscriptionModel())->validateSubscription(
                $this->episode->podcast->handle,
                $token
            )) === null) {
                return $this->response->setStatusCode(401, 'Invalid token!')
                    ->setJSON([
                        'errors' => [
                            'status' => 401,
                            'title' => 'Unauthorized',
                            'detail' => 'Invalid token!',
                        ],
                    ]);
            }
        }

        $session = Services::session();
        $session->start();

        $serviceName = '';
        if ($this->request->getGet('_from')) {
            $serviceName = $this->request->getGet('_from');
        } elseif ($session->get('embed_domain') !== null) {
            $serviceName = $session->get('embed_domain');
        } elseif ($session->get('referer') !== null && $session->get('referer') !== '- Direct -') {
            $serviceName = parse_url((string) $session->get('referer'), PHP_URL_HOST);
        }

        $audioFileSize = $this->episode->audio->file_size;
        $audioFileHeaderSize = $this->episode->audio->header_size;
        $audioDuration = $this->episode->audio->duration;

        // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics
        // - if audio is less than or equal to 60s, then take the audio file_size
        // - if audio is more than 60s, then take the audio file_header_size + 60s
        $bytesThreshold = $audioDuration <= 60
            ? $audioFileSize
            : $audioFileHeaderSize +
                (int) floor((($audioFileSize - $audioFileHeaderSize) / $audioDuration) * 60);

        helper('analytics');
        podcast_hit(
            $this->episode->podcast_id,
            $this->episode->id,
            $bytesThreshold,
            $audioFileSize,
            $audioDuration,
            $this->episode->published_at->getTimestamp(),
            $serviceName,
            $subscription !== null ? $subscription->id : null
        );

        $analyticsConfig = config('Analytics');

        return redirect()->to($analyticsConfig->getAudioUrl($this->episode, $this->request->getGet()));
    }
}
+1 −35
Original line number Diff line number Diff line
@@ -15,45 +15,11 @@ use App\Models\EpisodeModel;
use CodeIgniter\Controller;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Modules\Analytics\Config\Analytics;
use Psr\Log\LoggerInterface;

class EpisodeAnalyticsController extends Controller
{
    public mixed $config;

    /**
     * An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
     * other controllers that extend Analytics.
     *
     * @var string[]
     */
    protected $helpers = ['analytics'];

    protected Analytics $analyticsConfig;

    /**
     * Constructor.
     */
    public function initController(
        RequestInterface $request,
        ResponseInterface $response,
        LoggerInterface $logger
    ): void {
        // Do Not Edit This Line
        parent::initController($request, $response, $logger);

        set_user_session_deny_list_ip();
        set_user_session_location();
        set_user_session_player();

        $this->config = config('Analytics');
    }

    /**
     * @deprecated Replaced by EpisodeController::audio method
     * @deprecated Replaced by EpisodeAudioController::index method
     */
    public function hit(string $base64EpisodeData, string ...$audioPath): RedirectResponse
    {