From d656b4fdfc8443d59155f68ba3fd4346026a6798 Mon Sep 17 00:00:00 2001 From: "eric.gilbertson" Date: Sun, 19 May 2019 18:35:48 -0700 Subject: [PATCH] #6 - timestamp playlist inserts when adding to a live showEndTime add timestamp to track inserts on playlist if show is realtime, eg insert is done during the user specified begin/end times. also simplify markup generation by breaking large functions up and by using CSS to reduce the amount of required markup. other changes include: - show time in 12 hour format - show album & artist names as 'first last' (was 'last, first') - remove the butt ugly heart logo - simplify playlist banner - add playlist CSS classes - normailize border width - add border between playlist header and body - improved track edit glyphs - replace \" with ' for readability - update zkdbSchema.sql and provide an upgrade script - protect against empty arrays in for loops (generate ugly warning messages) - make album link clickable in playlist edit mode (now matches view mode markup) - added 'created' to tracks query (as last column in the select statement) - added function to check if insert is within show start/end times --- css/kzsustyle.css | 6 +- css/zoostyle.css | 73 +++++++++++- db/convert_v1_to_v2.sql | 76 ++++++++++++ db/convert_v2_to_v3.sql | 37 ++++++ db/zkdbSchema.sql | 1 + engine/impl/Library.php | 2 +- engine/impl/Playlist.php | 60 ++++++++-- ui/Playlists.php | 245 ++++++++++++++++++++++++++------------- 8 files changed, 399 insertions(+), 101 deletions(-) create mode 100644 db/convert_v1_to_v2.sql create mode 100644 db/convert_v2_to_v3.sql diff --git a/css/kzsustyle.css b/css/kzsustyle.css index a9bd4084..0bd361e5 100644 --- a/css/kzsustyle.css +++ b/css/kzsustyle.css @@ -4,9 +4,6 @@ body { color: black; background-color: #4D0A0A; - background-image: url(../img/kzsu/kzsu_heart.png); - background-repeat: no-repeat; - background-position: 30px 455px; } a { color: #990010; @@ -44,6 +41,9 @@ img.right { /* Header styles */ +.playlistBanner { + background-color: #4D0A0A; +} div.header { font-family: "century gothic", "urw gothic l", diff --git a/css/zoostyle.css b/css/zoostyle.css index af3df26f..a6299105 100644 --- a/css/zoostyle.css +++ b/css/zoostyle.css @@ -239,9 +239,7 @@ DIV.headerLogo IMG { DIV.content { float: right; width: 650px; - padding: 30px; - padding-top: 10px; - padding-right: 10px; + padding: 10px; min-height: 450px; margin-left: auto; margin-right: auto; @@ -327,6 +325,28 @@ P.zktitle A { } /* general */ +.playlistBanner { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11pt; + font-weight: bold; + color: white; + background-color: #2530A7; + line-height: 24px; + margin-bottom: 4px; +} +.playlistBanner > div { + font-size: 11pt; + float: right; + font-size: 10px; +} + +.playlistHdr { + border-bottom: 1px solid gray; +} +.playlistTable { + width: 100%; +} + TH { font-family: verdana, arial, helvetica, sans-serif; font-size: 11pt; @@ -485,3 +505,50 @@ A.calLink { border: 0px; background-image: URL('../img/list_dn.gif'); } + +/* up/down & edit links for playlist edits */ +.songManager { + top: 8px; + height: 20px; + width: 24px; + position: relative; + font-size:12px; +} + +.songManager a { + position: absolute; + line-height: 1.4; + background-repeat: no-repeat; + width: 10px; + height: 4px; +} +.songUp { + top: 0px; + background-image: URL('/img/arrow_up_beta.gif'); +} + +.songDown { + background-image: URL('/img/arrow_down_beta.gif'); + top: 8px; +} +.songEdit { + top: -2px; + left: 12px; + font-size:14px; +} + +.songLabel { + font-size:10px; +} +.songReview { + background-image: URL('/img/rinfo_beta.gif'); + width:11px; + height:11px; +} +.songDivider hr { + position: relative; + top: 3px; + height: 0px; + background-color: gray; +} + diff --git a/db/convert_v1_to_v2.sql b/db/convert_v1_to_v2.sql new file mode 100644 index 00000000..3be8b96d --- /dev/null +++ b/db/convert_v1_to_v2.sql @@ -0,0 +1,76 @@ +/* + * Zookeeper Online + * + * @author Jim Mason + * @copyright Copyright (C) 1997-2018 Jim Mason + * @link https://zookeeper.ibinx.com/ + * @license GPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * version 3, along with this program. If not, see + * http://www.gnu.org/licenses/ + */ + +/** + * IMPORTANT NOTE: + * + * Run this script ONLY if you are converting an existing Zookeeper Online v1 + * database for use with the current codebase. + * + * If you are creating a new database, run zkdbSchema.sql and then populate + * the resulting db using the various bootstrap scripts as appropriate. + */ + +-- +-- Database: `zkdb` +-- + +SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; +SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; +SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION; +SET NAMES utf8; + +ALTER DATABASE `zkdb` CHARACTER SET utf8 COLLATE utf8_general_ci; + +ALTER TABLE `airnames` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `albumvol` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `categories` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `chartemail` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `colltracknames` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `currents` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `lists` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `plays` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `publist` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `reviews` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `sessions` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `ssoredirect` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `ssosetup` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `tagqueue` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `tracknames` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `tracks` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE `users` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; + +ALTER TABLE `categories` ADD COLUMN `email` varchar(80) DEFAULT NULL; + +UPDATE `categories` SET email='reggae@kzsu.stanford.edu' WHERE id = 9; +UPDATE `categories` SET email='classical@kzsu.stanford.edu' WHERE id = 8; +UPDATE `categories` SET email='world@kzsu.stanford.edu' WHERE id = 7; +UPDATE `categories` SET email='jazz@kzsu.stanford.edu' WHERE id = 6; +UPDATE `categories` SET email='hiphop@kzsu.stanford.edu' WHERE id = 5; +UPDATE `categories` SET email='metal@kzsu.stanford.edu' WHERE id = 4; +UPDATE `categories` SET email='rpm@kzsu.stanford.edu' WHERE id = 3; +UPDATE `categories` SET email='country@kzsu.stanford.edu' WHERE id = 2; +UPDATE `categories` SET email='blues@kzsu.stanford.edu' WHERE id = 1; + +SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT; +SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS; +SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION; diff --git a/db/convert_v2_to_v3.sql b/db/convert_v2_to_v3.sql new file mode 100644 index 00000000..a630cc9e --- /dev/null +++ b/db/convert_v2_to_v3.sql @@ -0,0 +1,37 @@ +/* + * Zookeeper Online + * + * @author Eric Gilbertson + * @copyright Copyright (C) 1997-2018 Jim Mason + * @link https://zookeeper.ibinx.com/ + * @license GPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * version 3, along with this program. If not, see + * http://www.gnu.org/licenses/ + */ + +/** + * IMPORTANT NOTE: + * + * Run this script ONLY if you are converting an existing Zookeeper Online v2 + * database for use with the current codebase. + * + * If you are creating a new database, run zkdbSchema.sql and then populate + * the resulting db using the various bootstrap scripts as appropriate. + */ + +-- +-- Database: `zkdb` +-- + +alter table tracks add created timestamp null default NULL; diff --git a/db/zkdbSchema.sql b/db/zkdbSchema.sql index 4ab302e5..87be5000 100644 --- a/db/zkdbSchema.sql +++ b/db/zkdbSchema.sql @@ -363,6 +363,7 @@ CREATE TABLE IF NOT EXISTS `tracks` ( `track` varchar(80) DEFAULT NULL, `album` varchar(80) DEFAULT NULL, `label` varchar(80) DEFAULT NULL, + `created` timestamp DEFAULT NULL, PRIMARY KEY (`id`), KEY `list` (`list`), KEY `tag` (`tag`), diff --git a/engine/impl/Library.php b/engine/impl/Library.php index c4985d7b..848050ed 100644 --- a/engine/impl/Library.php +++ b/engine/impl/Library.php @@ -246,7 +246,7 @@ public function markAlbumsReviewed(&$albums, $loggedIn = 0) { $chain = []; $tags = []; $queryset = ""; - for($i = 0; $i < sizeof($albums); $i++) { + for($i = 0; $albums != null && $i < sizeof($albums); $i++) { $tag = array_key_exists("tag", $albums[$i])?$albums[$i]["tag"]:0; if($tag) { if(array_key_exists($tag, $tags)) diff --git a/engine/impl/Playlist.php b/engine/impl/Playlist.php index 747eca0c..a1769b2b 100644 --- a/engine/impl/Playlist.php +++ b/engine/impl/Playlist.php @@ -24,6 +24,8 @@ namespace ZK\Engine; +use \Datetime; +use \DateTimeZone; use ZK\Engine\ILibrary; @@ -162,7 +164,7 @@ public function updatePlaylist($playlist, $date, $time, $description, $airname) } public function getTrack($id) { - $query = "SELECT tag, artist, track, album, label, id FROM tracks " . + $query = "SELECT tag, artist, track, album, label, id, created FROM tracks " . "WHERE id = ?"; $stmt = $this->prepare($query); $stmt->bindValue(1, (int)$id, \PDO::PARAM_INT); @@ -170,7 +172,7 @@ public function getTrack($id) { } public function getTracks($playlist, $desc = 0) { - $query = "SELECT tag, artist, track, album, label, id FROM tracks " . + $query = "SELECT tag, artist, track, album, label, id, created FROM tracks " . "WHERE list = ? ORDER BY id"; if($desc) $query .= " DESC"; @@ -178,23 +180,59 @@ public function getTracks($playlist, $desc = 0) { $stmt->bindValue(1, (int)$playlist, \PDO::PARAM_INT); return $this->execute($stmt); } - - public function insertTrack($playlist, $tag, $artist, $track, $album, $label) { + + // return true if "now" is within the show start/end time & date. + // NOTE: this routine must be tolerant of improperly formatted dates. + public function isWithinShow($listRow) { + $TIME_FORMAT = "Y-m-d Gi"; // eg, 2019-01-01 1234 + $retVal = false; + + try { + $timeAr = explode("-", $listRow[2]); + if (count($timeAr) == 2) { + $timeStr1 = $listRow[1] . " " . $timeAr[0]; + $start = DateTime::createFromFormat($TIME_FORMAT, $timeStr1); + $endStr = $timeAr[1] == "0000" ? "2359" : $timeAr[1]; + $timeStr2 = $listRow[1] . " " . $endStr; + $end = DateTime::createFromFormat($TIME_FORMAT, $timeStr2); + + if (isset($start) && isset($end)) { + $now = new DateTime("now"); + $retVal = (($now > $start) && ($now < $end)); + } + } + } catch (Throwable $t) { + ; + } + return $retVal; + } + + public function insertTrack($playlistId, $tag, $artist, $track, $album, $label) { + $row = Engine::api(IPlaylist::class)->getPlaylist($playlistId, 1); + + // log time iff 'now' is within playlist start/end time. + $doTimestamp = self::isWithinShow($row); + $timeName = $doTimestamp ? "created, " : ""; + $timeValue = $doTimestamp ? "NOW(), " : ""; + // Insert tag? - $noTag = ($tag == 0) || ($tag == ""); + $haveTag = ($tag != 0) && ($tag != ""); + $tagName = $haveTag ? ", tag" : ""; + $tagValue = $haveTag ? ", ?" : ""; - $query = "INSERT INTO tracks " . - "(list, artist, track, album, label" . ($noTag?")":", tag)") . - " VALUES (?, ?, ?, ?, ?" . - ($noTag?")":", ?)"); + $names = "(" . $timeName . "list, artist, track, album, label " . $tagName . ")"; + $values = " VALUES (" . $timeValue . "?, ?, ?, ?, ?" . $tagValue . ");"; + + $query = "INSERT INTO tracks " . ($names) . ($values); $stmt = $this->prepare($query); - $stmt->bindValue(1, (int)$playlist, \PDO::PARAM_INT); + $stmt->bindValue(1, (int)$playlistId, \PDO::PARAM_INT); $stmt->bindValue(2, $artist); $stmt->bindValue(3, $track); $stmt->bindValue(4, $album); $stmt->bindValue(5, $label); - if(!$noTag) + if($haveTag) $stmt->bindValue(6, $tag); + return $stmt->execute(); } diff --git a/ui/Playlists.php b/ui/Playlists.php index 5fb20eab..105b63c5 100644 --- a/ui/Playlists.php +++ b/ui/Playlists.php @@ -122,6 +122,24 @@ private function composeTime($fromTime, $toTime) { return $fromTime . "-" . $toTime; } + public static function showStartTime($timeRange) { + $retVal = "??"; + $timeAr = explode("-", $timeRange); + if (count($timeAr) == 2) + $retVal = self::hourToAMPM($timeAr[0]); + + return $retVal; + } + + public static function showEndTime($timeRange) { + $retVal = "??"; + $timeAr = explode("-", $timeRange); + if (count($timeAr) == 2) + $retVal = self::hourToAMPM($timeAr[1]); + + return $retVal; + } + public static function hourToAMPM($hour, $full=0) { $h = (int)floor($hour/100); $m = (int)$hour % 100; @@ -147,6 +165,22 @@ public static function timeToAMPM($time) { } else return strtolower(htmlentities($time)); } + + public static function timestampToDate($time) { + if ($time == null || $time == '') { + return ""; + } else { + return date('D M d, Y ', strtotime($time)); + } + } + + public static function timestampToAMPM($time) { + if ($time == null || $time == '') { + return ""; + } else { + return date('h:ia', strtotime($time)); + } + } public static function timeToZulu($time) { if(strlen($time) == 9 && $time[4] == '-') { @@ -237,6 +271,7 @@ private function emitEditList($editlist) { list($year, $month, $day) = explode("-", $date); if(strlen($fromtime) && strlen($totime)) $time = $this->composeTime($fromtime, $totime); + if(checkdate($month, $day, $year) && ($time != "") && ($description != "")) { // Success - Run the query @@ -456,6 +491,19 @@ public function emitEditListSelNormal() { UI::setFocus("playlist"); } + private function makeEditDiv($row, $playlist) { + $sessionId = $this->session->getSessionID(); + $href = "?session=" . $sessionId . "&playlist=" . $playlist . "&id=" . + $row["id"] . "&action=" . $this->action . "&"; + $editLink = ""; + //NOTE: in edit mode the list is ordered new to old, so up makes it + //newer in time order & vice-versa. + $upLink = ""; + $downLink = ""; + $retVal = "
" . $upLink . $downLink . $editLink . "
"; + return $retVal; + } + public function emitEditListSelDeleted() { ?>
@@ -484,34 +532,17 @@ public function emitEditListSelDeleted() { UI::setFocus("playlist"); } - private function emitList($playlist, $id) { - ?> -
- - getTracks($playlist, 1); - while($records && ($row = $records->fetch())) { - if(!$header) { - echo " \n"; - $header = 1; - } - $class = ($id==$row["id"])?"sel":"nav"; - echo " \n"; - echo " \n"; - if(substr($row["artist"], 0, strlen(IPlaylist::SPECIAL_TRACK)) == IPlaylist::SPECIAL_TRACK) - echo " \n"; - else - echo " \n"; - } - echo "
 TagArtistTrackAlbum/Label
session->getSessionID()."&playlist=$playlist&id=".$row["id"]."&action=$this->action&seq=upTrack\">\n"; - echo " \"\"
\n"; - echo " \"\"
\n"; - echo " session->getSessionID()."&playlist=$playlist&id=".$row["id"]."&action=$this->action&seq=downTrack\">\n"; - echo " \"\"
session->getSessionID()."&playlist=$playlist&id=".$row["id"]."&action=$this->action&seq=editTrack\">>>
".$row["tag"]."" . - $this->smartURL($row["artist"]) . "" . - $this->smartURL($row["track"]) . "" . - $this->smartURL($row["album"], !$row["tag"]) . "
" . - $this->smartURL($row["label"]) . "
\n"; + // make header for edit & view playlist + private function makePlaylistHeader($isEditMode) { + $editCol = $isEditMode ? "" : ""; + $header = "" . $editCol . "Time" . + "ArtistTrackAlbum/Label"; + return $header; + } + + private function editPlaylist($playlist, $id) { + print("
"); + self::emitPlaylistBody($playlist, true); } private function emitTagForm($playlist, $message) { @@ -705,9 +736,10 @@ private function emitPlaylistTitle($playlist) { // Print the header $script = "?target=export"; $row = Engine::api(IPlaylist::class)->getPlaylist($playlist); + $showDateTime = self::makeShowDateAndTime($row); echo "\n \n \n \n"; - echo " \n \n
"; - echo "Playlist for $row[0]$row[1]Printable playlist
\n"; + echo "$row[0]\n $showDateTime\n"; + echo "
Print\n \n \n"; } private function insertSetSeparator($playlist) { @@ -817,7 +849,7 @@ public function emitEditor() { ?> - emitList($playlist, $id); ?> + editPlaylist($playlist, $id); ?> /" . self::smartURL($row["label"]) . ""; + if($row["tag"]) { + $albumTitle = "".$albumName .""; + + if ($includeLabel) { + $albumTitle = $albumTitle . $labelSpan; + } + } else { + $albumTitle = $this->smartURL($albumName); + if ($includeLabel) + $albumTitle = $albumTitle . $labelSpan; + } + return $albumTitle; + } + + + // converts "last, first" to "first last" + private function swapNames($fullName) { + $retVal = $fullName; + $names1 = explode(", ", $fullName); + if (count($names1) >= 2) { + $names2 = explode(" ", $names1[1]); + $extras = array_slice($names2, 1); + $retVal = $names2[0] . " " . $names1[0] . " " . join(" ", $extras); + } + return $retVal; + } + + private function emitPlaylistBody($playlist, $editMode) { + $REVIEW_DIV = "
"; + $header = self::makePlaylistHeader($editMode); + $editCell = ""; + echo "".$header; + + $records = Engine::api(IPlaylist::class)->getTracks($playlist, $editMode); + self::viewListGetAlbums($records, $albums); + Engine::api(ILibrary::class)->markAlbumsReviewed($albums); + + if($albums != null && sizeof($albums) > 0) { + foreach($albums as $index => $row) { + if ($editMode) + $editCell = ""; + + if(substr($row["artist"], 0, strlen(IPlaylist::SPECIAL_TRACK)) == IPlaylist::SPECIAL_TRACK) { + echo "".$editCell.""; + continue; + } + + $reviewCell = $row["REVIEWED"] ? $REVIEW_DIV : ""; + $artistName = self::swapNames($row["artist"]); + $timeplayed = self::timestampToAMPM($row["created"]); + $albumLink = self::makeAlbumLink($row, true); + echo "" . $editCell . + "" . + "" . + "" . + "" . + "" . + "\n"; + } + } + echo "
" . self::makeEditDiv($row, $playlist) . "

" . $timeplayed . "" . $this->smartURL($artistName) . "" . $this->smartURL($row["track"]) . "" . $reviewCell . "" . $albumLink . "
\n"; + } + + private function makeShowDateAndTime($row) { + $showStart = self::showStartTime($row[2]); + $showEnd = self::showEndTime($row[2]); + $showDate = self::timestampToDate($row[1]); + $showDateTime = $showDate . " " . $showStart . "-" . $showEnd; + return $showDateTime; + } + private function viewList($playlist) { $row = Engine::api(IPlaylist::class)->getPlaylist($playlist, 1); - if($row) { - list($y,$m,$d) = explode("-", $row[1]); - - $script = "?target=export"; - - echo "\n \n
Printable playlist
\n"; - echo "\n"; - echo "\n"; - echo "\n"; - echo "\n"; - echo "
Playlist for $row[0]" . - date("l, j F Y", mktime(0,0,0,$m,$d,$y)) . "  " . - self::timeToAMPM($row[2]) . "DJ: "; - echo "session->getSessionID()."&viewuser=$row[3]\" CLASS=\"nav2\">$row[4]"; - echo "
\n"; - - // Print the tracks - echo "\n"; - echo " \n"; - $records = Engine::api(IPlaylist::class)->getTracks($playlist); - $this->viewListGetAlbums($records, $albums); - Engine::api(ILibrary::class)->markAlbumsReviewed($albums); - if(sizeof($albums) > 0) - foreach($albums as $index => $row) { - if(substr($row["artist"], 0, strlen(IPlaylist::SPECIAL_TRACK)) == IPlaylist::SPECIAL_TRACK) { - echo " \n"; - continue; - } - echo " \n"; - } - echo "
ArtistTrackAlbum/Label

" . $this->smartURL($row["artist"]) . "" . - $this->smartURL($row["track"]) . ""; - if($row["REVIEWED"]) - echo "session->getSessionID(). - "\">\"[i]\""; - else - echo ""; - if($row["tag"]) echo "session->getSessionID(). - "\" CLASS=\"nav\">"; - - echo $this->smartURL($row["album"], !$row["tag"]); - if($row["tag"]) echo ""; - echo "
" . - $this->smartURL($row["LABELNAME"]) . "
\n"; - } else - echo "Sorry, the playlist you have requested does not exist."; + if( !$row) { + echo "Sorry, playlist does not exist " . $playlist . ""; + return; + } + + $showName = $row[0]; + $djId = $row[3]; + $djName = $row[4]; + $showDateTime = self::makeShowDateAndTime($row); + + // make print view header + echo "" . + "\n
session->getSessionID() . "&playlist='" . $playlist . + "&format=html)'>Print View
"; + + $dateDiv = "
".$showDateTime." 
"; + $djLink = "$djName"; + echo "
 " . $showName . " with " . $djLink.$dateDiv . "
"; + + self::emitPlaylistBody($playlist, false); } private function emitViewDJSortFn($a, $b) {