* @owner University of Latvia * @version 1.0.0 * @since 01.11.2019 * * @package Lu\lunabamusic\Domain\Model */ class Play extends AbstractEntity { /** * Table name * * @var string */ private $tableName = 'tx_lunabamusic_domain_model_play'; /** * Events table name * * @var string */ private $eventTableName = 'tx_calendarize_domain_model_event'; /** * Events configuration table * * @var string */ private $eventConfTableName = 'tx_calendarize_domain_model_configuration'; /** * Config values * * @var array */ private $conf = []; /** * Unit ID * * @var int */ public $uid; /** * Artist name * * @var string */ public $artist; /** * Song name * * @var string */ public $title; /** * Play time * * @var int */ public $time; /** * Previous time played * * @var int */ public $prev; /** * First time played * * @var int */ public $first; /** * Cron script * * @return bool */ public function executeCron() { // Pre-define models $dataHelper = new DataHelper(); // Call song fetch option and fail if none available! $apiSong = $this->fetchLiveEntry(); if (empty($apiSong)) { // Update last song end time $this->addEndTime(); return false; } // Get last song entry (this needs to be in cache) $lastSong = $this->getLastEntry(); // Get previous entry $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName)->createQueryBuilder(); $queryBuilder->select('*') ->where( $queryBuilder->expr()->eq('artist', $queryBuilder->createNamedParameter($apiSong['artist'])) ) ->andWhere( $queryBuilder->expr()->eq('title', $queryBuilder->createNamedParameter($apiSong['title'])) ) ->orderBy('time', 'DESC') ->from($this->tableName) ->setMaxResults(1); $statement = $queryBuilder->execute(); $result = $queryBuilder->execute()->fetchAll(); // Set first and previous song time entry (or current, if none found) $currentTime = time(); $previousTime = !empty($result[0]['prev']) ? $result[0]['prev'] : $currentTime; $firstTime = !empty($result[0]['first']) ? $result[0]['first'] : $previousTime; // Check if song has changed if ($apiSong['artist'] != $lastSong['artist'] && $apiSong['artist'] != $lastSong['artist']) { // Update last song end time $this->addEndTime(); $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->tableName); $affectedRows = $queryBuilder ->insert($this->tableName) ->values([ 'pid' => 0, 'artist' => $apiSong['artist'], 'title' => $apiSong['title'], 'first' => $firstTime, 'time' => $currentTime, 'endtime' => 0, 'prev' => $previousTime, 'checked' => 1, 'cron' => 1, ]) ->execute(); // Update cache $currentSong = $this->getLastEntry(); $dataHelper->saveInCache(DataHelper::CACHED_SONG_ENTRY, $currentSong, DataHelper::CACHED_SONG_TIME); } // Return true, if all succesfull return true; } /** * Returns programm entry from calendar * This requires Calendarize dependency! * * @return string */ public function getCurrentProgramme() { // Set default programm name $hoursNow = $sincemidnight = time() - mktime(0, 0, 0); $DataHelper = new DataHelper(); $title = !empty($DataHelper->getLangString('programme.music')) ? $DataHelper->getLangString('programme.music') : 'Mūzika'; // Get config $this->getConfig(); if ($this->conf['eventPageId']) { $eventPageId = $this->conf['eventPageId']; // Fetch event configs for current time $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->eventConfTableName)->createQueryBuilder(); $queryBuilder->select('*')->where("`deleted` = 0 AND `start_date` <= UNIX_TIMESTAMP() AND (`all_day` = 1 OR (`start_time` <= {$hoursNow} AND (`end_time` >= {$hoursNow} OR `end_time` = 0))) AND `pid` = {$eventPageId}")->from($this->eventConfTableName); $statement = $queryBuilder->execute(); // If timestamp matches, getch actual event while ($row = $statement->fetch()) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->eventTableName)->createQueryBuilder(); $queryBuilder->select('*')->where("`deleted` = 0 AND `hidden` = 0 AND `calendarize` = {$row['uid']}")->from($this->eventTableName); $programmResults = $queryBuilder->execute(); // If entry found, assign the name while ($data = $programmResults->fetch()) { $newCDate = new \DateTime(); $newSDate = DateTime::createFromFormat('Y-m-d', $row['start_date'], new \DateTimeZone('UTC')); $newEDate = new DateTime('today'); if (!empty($row['end_date'])) { $newEDate = DateTime::createFromFormat('Y-m-d', $row['end_date'], new \DateTimeZone('UTC')); } // Check if day of week matches if ($newSDate->format('w') <= $newCDate->format('w') && $newCDate->format('w') <= $newEDate->format('w')) { $title = $data['title']; } break; } } } // Return title return $title; } /** * Retreive song */ public function getCurrentSong($onLine = false) { // Pre-define models $dataHelper = new DataHelper(); $defaultProgramme = !empty($dataHelper->getLangString('programme.music')) ? $dataHelper->getLangString('programme.music') : 'Mūzika'; // If current programme isn't music return programme title if ($this->getCurrentProgramme() != $defaultProgramme) { return $this->getCurrentProgramme(); } // Fetch info from cache or create new one, if none present $cachedSong = $dataHelper->getFromCache(DataHelper::CACHED_SONG_ENTRY); if (!$cachedSong) { $currentSong = $this->getLastEntry(); $dataHelper->saveInCache(DataHelper::CACHED_SONG_ENTRY, $currentSong, DataHelper::CACHED_SONG_TIME); $cachedSong = $currentSong; } // If last song has ended return programme title if ($cachedSong['endtime'] != 0) { return $this->getCurrentProgramme(); } // Check if one-line return requested if ($onLine) { return $cachedSong['artist']." - ".$cachedSong['title']; } return $cachedSong; } /** * Returns array of most played songs of the current month * * @param int $limit * * @param string $pickedMonth * * @return array */ public function getMostPlayedSongs(int $limit, string $pickedMonth = null) { if ($pickedMonth) { $firstDayOfMonth = DateTime::createFromFormat('m/d/Y', $pickedMonth); $firstDayOfMonth->modify('first day of this month midnight'); $lastDayOfMonth = DateTime::createFromFormat('m/d/Y', $pickedMonth); $lastDayOfMonth->modify('first day of next month midnight'); } else { $firstDayOfMonth = new DateTime('first day of this month midnight'); $lastDayOfMonth = new DateTime('first day of next month midnight'); } $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName)->createQueryBuilder(); $queryBuilder->addSelectLiteral( // $queryBuilder->count() can't assign an alias. ExpressionBuilder used instead. $queryBuilder->expr()->count('*', 'timesPlayed') ) ->addSelect('artist', 'title', 'first') ->from($this->tableName) ->where( $queryBuilder->expr()->gte('time', $queryBuilder->createNamedParameter($firstDayOfMonth->getTimestamp())), $queryBuilder->expr()->lte('time', $queryBuilder->createNamedParameter($lastDayOfMonth->getTimestamp())) ) ->groupBy('artist', 'title', 'first') ->orderBy('timesPlayed', 'DESC') ->addOrderBy('artist', 'ASC') ->setMaxResults($limit); return $queryBuilder->execute()->fetchAll(); } /** * Searches songs by date and/or query in artist and title * * @param $date * @param string $query * * @return array */ public function searchSongs($date = null, string $query = null) { $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName); $queryBuilder = $connection->createQueryBuilder(); $queryBuilder->select('artist', 'title', 'time', 'prev') ->from($this->tableName); // If date was passed in search only in that date if ($date) { $start = new DateTime($date); $end = new DateTime($date); $start->modify('midnight'); $end->modify('tomorrow -1 minute'); $queryBuilder->where( $queryBuilder->expr()->gte('time', $queryBuilder->createNamedParameter($start->getTimestamp())), $queryBuilder->expr()->lte('time', $queryBuilder->createNamedParameter($end->getTimestamp())) ); } // If query was passed in add it to SQL query if ($query) { $queryBuilder->andWhere( $queryBuilder->expr()->comparison( $connection->getDatabasePlatform()->getConcatExpression('artist, \' \', title'), 'LIKE', $queryBuilder->createNamedParameter('%'.$queryBuilder->escapeLikeWildcards($query).'%') ) ); } // If query was passed in without date exclude all entries with 0 time if ($query and !$date) { $queryBuilder->andWhere("`time` > 0"); } $queryBuilder->orderBy('time', 'DESC'); return $queryBuilder->execute()->fetchAll(); } /** * Get songs played on current day * * @return array */ public function getTodaysSongs() { return $this->searchSongs('now'); } /** * Get last played song * * @return mixed */ public function getLastEntry() { return $this->take(1); } /** * Retrieve defined number of rows * * @param $limit * * @return mixed */ public function take($limit) { $data = []; // Build query $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName)->createQueryBuilder(); $queryBuilder->select('*')->orderBy('uid', 'DESC')->from($this->tableName)->setMaxResults($limit); $statement = $queryBuilder->execute(); // Build data array while ($row = $statement->fetch()) { // Return entry if requested only one if ($limit == 1) { return $row; } else { $data[] = $row; } } // Return array return $data; } /** * Fetches data from provided endpoint * * Notice: This should be called with care! * * @return array|int */ private function fetchLiveEntry() { // Get url or fail $this->getConfig(); $url = $this->conf['streamServer']; if (!$url) { return []; } $client = new Guzzle(); // Fetch actual image content $result = $client->get($url, ['verify' => false]); $songFromServer = $result->getBody()->getContents(); // Fetch song response or fail $songArtistTitle = explode(';', trim($songFromServer, ';')); if (empty(trim($songArtistTitle[0])) or empty(trim($songArtistTitle[1]))) { return []; } // Build array and return $data = [ 'artist' => trim($songArtistTitle[0]), 'title' => trim($songArtistTitle[1]), ]; return $data; } /** * Get config values */ private function getConfig() { // Fetch config $this->conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('lu_nabamusic'); } /** * Add end time to last played song */ private function addEndTime() { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName)->createQueryBuilder(); // Get last song uid $queryBuilder->select('*')->orderBy('uid', 'DESC')->from($this->tableName)->setMaxResults(1); $statement = $queryBuilder->execute(); $queryBuilder->update($this->tableName) ->where( $queryBuilder->expr()->eq('uid', $statement->fetch()['uid']) ) ->andWhere( $queryBuilder->expr()->eq('endtime', 0) ) ->set('endtime', time()) ->execute(); } }