diff --git a/css/kzsustyle.css b/css/kzsustyle.css index a3a2cae3..fa546b39 100644 --- a/css/kzsustyle.css +++ b/css/kzsustyle.css @@ -231,8 +231,12 @@ P.zktitle A { background-image: URL('../img/kzsu/kzsu_rinfo.gif'); } .editorUp { - background-image: URL('../img/kzsu/kzsu_list_up.gif'); + background-color: #980000; } .editorDown { - background-image: URL('../img/kzsu/kzsu_list_dn.gif'); + background-color: #980000; +} + +.playTrack DIV { + filter: invert(0.3) sepia(1) saturate(5) hue-rotate(310deg); } diff --git a/css/zoostyle.css b/css/zoostyle.css index 77e32795..d745f113 100644 --- a/css/zoostyle.css +++ b/css/zoostyle.css @@ -241,16 +241,22 @@ TD.label-form { .editorUp { top: 0px; width: 220px; - height: 10px; + height: 16px; border: 0px; - background-image: URL('../img/list_up.gif'); + color: #ffffff; + font-family: courier; + font-weight: 900; + background-color: #2530a7; } .editorDown { top: 0px; width: 220px; - height: 10px; + height: 16px; border: 0px; - background-image: URL('../img/list_dn.gif'); + font-family: courier; + font-weight: 900; + color: #ffffff; + background-color: #2530a7; } .editorChooser { width: 220px; @@ -259,6 +265,34 @@ TD.label-form { scrollbar-width: none; } +.trackEditor { + width: 100%; +} + +.trackEditor TH { + font-size: 10pt; + text-align: left; + vertical-align: bottom; + line-height: 10px; +} + +.trackTable TD.playTrack { + vertical-align: top; +} + +.playTrack DIV { + filter: invert(59%) sepia(98%) saturate(1900%) hue-rotate(204deg) brightness(94%) contrast(88%); /* #828ae3 see https://codepen.io/sosuke/pen/Pjoqqp */ + width: 18px; + height: 18px; + background-size: 18px; + background-image: URL("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4IiB3aWR0aD0iNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGg0OHY0OEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMCAzM2wxMi05LTEyLTl2MTh6bTQtMjlDMTIuOTUgNCA0IDEyLjk1IDQgMjRzOC45NSAyMCAyMCAyMCAyMC04Ljk1IDIwLTIwUzM1LjA1IDQgMjQgNHptMCAzNmMtOC44MiAwLTE2LTcuMTgtMTYtMTZTMTUuMTggOCAyNCA4czE2IDcuMTggMTYgMTYtNy4xOCAxNi0xNiAxNnoiLz48L3N2Zz4="); +} + +.playTrack A:hover { + filter: saturate(10); /* doesn't work here in webkit */ + text-decoration: none; +} + /* layout */ DIV.box { width: 900px; @@ -456,7 +490,7 @@ TH.sec { .success { color: #006600; } -.text { +.text, .urlValue { padding: 2px; border: 1px solid #3b0000; } diff --git a/db/convert_v2_9_0_to_v2_10_0.sql b/db/convert_v2_9_0_to_v2_10_0.sql new file mode 100644 index 00000000..e22693e1 --- /dev/null +++ b/db/convert_v2_9_0_to_v2_10_0.sql @@ -0,0 +1,47 @@ +/* + * Zookeeper Online + * + * @author Jim Mason + * @copyright Copyright (C) 1997-2020 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_9_0 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 utf8mb4; + +ALTER TABLE `tracknames` ADD COLUMN `url` varchar(2083) NOT NULL DEFAULT ''; +ALTER TABLE `colltracknames` ADD COLUMN `url` varchar(2083) NOT NULL DEFAULT ''; + +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/zkdbSchema.sql b/db/zkdbSchema.sql index 2e1fb430..f711e1d9 100644 --- a/db/zkdbSchema.sql +++ b/db/zkdbSchema.sql @@ -129,6 +129,7 @@ CREATE TABLE IF NOT EXISTS `colltracknames` ( `tag` int(11) DEFAULT NULL, `track` varchar(80) DEFAULT NULL, `artist` varchar(80) DEFAULT NULL, + `url` varchar(2083) NOT NULL DEFAULT '', `seq` smallint(6) DEFAULT NULL, PRIMARY KEY (`id`), KEY `tag` (`tag`), @@ -344,6 +345,7 @@ CREATE TABLE IF NOT EXISTS `tracknames` ( `id` int(11) NOT NULL AUTO_INCREMENT, `tag` int(11) DEFAULT NULL, `track` varchar(80) DEFAULT NULL, + `url` varchar(2083) NOT NULL DEFAULT '', `seq` smallint(6) DEFAULT NULL, PRIMARY KEY (`id`), KEY `tag` (`tag`), diff --git a/engine/impl/Editor.php b/engine/impl/Editor.php index 94134804..cc0c98ef 100644 --- a/engine/impl/Editor.php +++ b/engine/impl/Editor.php @@ -120,12 +120,13 @@ public function insertUpdateAlbum(&$album, $tracks, $label) { // Album $title = trim($album["album"]); $artist = trim($album["artist"]); + $iscoll = "0"; if(!$album["location"]) $album["location"] = "L"; if(array_key_exists("coll", $album) && $album["coll"]) { $artist = "[coll]: $title"; $iscoll = "1"; - } else $iscoll = "0"; + } $newAlbum = !$album["tag"]; @@ -175,6 +176,10 @@ public function insertUpdateAlbum(&$album, $tracks, $label) { // Tracks if($tracks || $newAlbum) { + // We delete from both tracknames and colltracknames + // because someone could have toggled the 'compilation' + // checkbox; this ensures no stale track names remain + // in the other table. $query = "DELETE FROM colltracknames WHERE tag=?"; $stmt = $this->prepare($query); $stmt->bindValue(1, $album["tag"]); @@ -183,25 +188,23 @@ public function insertUpdateAlbum(&$album, $tracks, $label) { $stmt = $this->prepare($query); $stmt->bindValue(1, $album["tag"]); $stmt->execute(); - //echo "DEBUG: affected rows: ".$stmt->rowCount()."
"; - - //echo "DEBUG: query=$query
"; + for($i=1; $tracks && array_key_exists($i, $tracks); $i++) { - if($iscoll) { - $query = "INSERT INTO colltracknames (tag, seq, track, " . - "artist) VALUES (?, ?, ?, ?)"; - $stmt = $this->prepare($query); - $stmt->bindValue(3, trim($tracks[$i]["track"])); - $stmt->bindValue(4, trim($tracks[$i]["artist"])); - } else { - $query = "INSERT INTO tracknames (tag, seq, track) ". - "VALUES (?, ?, ?)"; - $stmt = $this->prepare($query); - $stmt->bindValue(3, trim($tracks[$i])); - } + $trackRow = $tracks[$i]; + $trackName = trim($trackRow['track']); + $trackUrl = trim($trackRow['url']); + $query = "INSERT INTO tracknames (tag, seq, track, url) VALUES (?, ?, ?, ?)"; + if ($iscoll) + $query = "INSERT INTO colltracknames (tag, seq, track, url, artist) VALUES (?, ?, ?, ?, ?)"; + + $stmt = $this->prepare($query); $stmt->bindValue(1, $album["tag"]); $stmt->bindValue(2, $i); - //echo "DEBUG: query=$query
"; + $stmt->bindValue(3, $trackName); + $stmt->bindValue(4, $trackUrl); + if ($iscoll) + $stmt->bindValue(5, trim($trackRow["artist"])); + $stmt->execute(); } } @@ -302,7 +305,7 @@ public function getAlbum($tag) { } public function getTracks($tag, $isColl) { - $table = $isColl?"colltracknames":"tracknames"; + $table = $isColl?"colltracknames":"tracknames"; $query = "SELECT * FROM $table WHERE tag = ? ORDER BY seq"; $stmt = $this->prepare($query); $stmt->bindValue(1, $tag); diff --git a/js/editor.common.js b/js/editor.common.js index 48110f9a..01c48d9f 100644 --- a/js/editor.common.js +++ b/js/editor.common.js @@ -211,11 +211,13 @@ $().ready(function() { } for(var j=next; j>focus; j--) { $("INPUT[name='track" + j + "' i]").val($("INPUT[name='track" + (j-1) + "' i]").val()); + $("INPUT[name='trackUrl" + j + "' i]").val($("INPUT[name='trackUrl" + (j-1) + "' i]").val()); if(coll) { $("INPUT[name='artist" + j + "' i]").val($("INPUT[name='artist" + (j-1) + "' i]").val()); } } $("INPUT[name='track" + focus + "' i]").val(""); + $("INPUT[name='trackUrl" + focus + "' i]").val(""); if(coll) { $("INPUT[name='artist" + focus + "' i]").val(""); } @@ -228,11 +230,13 @@ $().ready(function() { var last = nextTrack()-1; for(var j=focus; jgetAlbum(); - $result = Engine::api(IEditor::class)->insertUpdateAlbum($album, $this->getTracks(), $this->getLabel()); + $tracks = $this->getTracks(); + $result = Engine::api(IEditor::class)->insertUpdateAlbum($album, $tracks, $this->getLabel()); if($result) { if($_REQUEST["new"]) { @@ -684,15 +685,22 @@ private function getLabel() { return $label; } + // returns list of tuples containing track, url and artist. note that + // artist will be empty for single artist disks. private function getTracks() { $tracks = array(); $isColl = array_key_exists("coll", $_REQUEST) && $_REQUEST["coll"]; - for($i=1; - array_key_exists("track".$i, $_POST) && - !self::isEmpty($_POST["track". $i]); $i++) - $tracks[$i] = $isColl?[ "track" => $_POST["track".$i], - "artist" => $_POST["artist".$i] ]: - $_POST["track".$i]; + + for($i=1; array_key_exists("track".$i, $_POST); $i++) { + $track = $_POST["track". $i]; + if ($track['track'] == '') { + break; + } else { + $url = $_POST["trackUrl". $i]; + $artist = $isColl ? $_POST["artist".$i] : ""; + $tracks[$i] = ["track" => $track, "url" => $url, "artist" => $artist]; + } + } return $tracks; } @@ -753,19 +761,21 @@ private function getTitle($seq) { private function emitAlbumSel() { UI::emitJS('js/editor.album.js'); - echo "
\n"; - echo " \n"; - echo ""; - echo "\n"; - echo "
Search:

\n"; - echo "compilation?

\n"; + echo "\n"; + echo ""; + echo "\n"; + echo "\n"; + echo "
Search Albums:

\n"; + echo "compilation?


\n"; - echo " \n"; - echo " \n"; - echo " limit\">\n"; + echo " \n"; + echo " \n"; + echo " \n"; ?>
@@ -906,20 +916,20 @@ private function albumForm() { private function emitLabelSel() { UI::emitJS('js/editor.label.js'); - echo "
\n"; - echo " \n"; - echo ""; - echo "\n"; - echo "
Search:

\n"; + echo " \n"; + echo ""; + echo "\n"; + echo " \n"; + echo "
Search Labels:


\n"; - echo " \n"; - echo " \n"; - echo " limit\">\n"; - echo " \n"; - echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; ?>
@@ -993,8 +1003,9 @@ private function labelForm() { private function validateTracks() { $lowestBlank = $highestTrack = 0; + $trackPattern = "/track\d/"; foreach($_POST as $key => $value) { - if(substr($key, 0, 5) == "track") { + if(preg_match_all($trackPattern, $key)) { $i = substr($key, 5) * 1; if(!self::isEmpty($value) && $i > $highestTrack) $highestTrack = $i; @@ -1005,51 +1016,81 @@ private function validateTracks() { return !$lowestBlank || $lowestBlank >= $highestTrack; } + private function emitTrackList($focusTrack, $isCollection) { + $artistHdr = $isCollection ? "" : ""; + $cellWidth = "width:" . ($isCollection ? "220px" : "330px"); + $idp = "padding-right:" . ($isCollection ? "3px" : "7px"); + + echo "
Artist
\n"; + echo "${artistHdr}\n"; + + for($i=0; $i<$this->tracksPerPage; $i++) { + $trackNum = $_REQUEST["nextTrack"] + $i; + $this->skipVar("track".$trackNum); + $this->skipVar("trackUrl".$trackNum); + $title = htmlentities(stripslashes($_POST["track".$trackNum])); + $focus = $focusTrack == $trackNum ? " data-focus" : ""; + + echo ""; + echo ""; + echo ""; + + if($isCollection) { + $artist = htmlentities(stripslashes($_POST["artist".$trackNum])); + echo ""; + $this->skipVar("artist".$trackNum); + } + + $url = $_POST["url$trackNum"]; + echo ""; + + echo "\n"; + } + echo "
Track NameURLInsert/Delete Track:  
$trackNum:
"; + } + private function trackForm() { + $isCollection = $_REQUEST["coll"]; + if($_REQUEST["seltag"] && !$_REQUEST["tdb"]) { - $tracks = Engine::api(IEditor::class)->getTracks($_REQUEST["seltag"], $_REQUEST["coll"]); + $tracks = Engine::api(IEditor::class)->getTracks($_REQUEST["seltag"], $isCollection); while($row = $tracks->fetch()) { $this->emitHidden("track".$row["seq"], $row["track"]); + $_POST["url".$row["seq"]] = $row["url"]; $_POST["track".$row["seq"]] = $row["track"]; - if($_REQUEST["coll"]) { + if($isCollection) { $this->emitHidden("artist" . $row["seq"], $row["artist"]); $_POST["artist".$row["seq"]] = $row["artist"]; } } $this->emitHidden("tdb", "true"); } + if($_REQUEST["nextTrack"]) { // validate previous batch of tracks were entered $lastBatch = $_REQUEST["nextTrack"] - $this->tracksPerPage; for($i=0; $i<$this->tracksPerPage; $i++) { if(self::isEmpty($_POST["track".(int)($lastBatch+$i)]) || - $_REQUEST["coll"] && self::isEmpty($_POST["artist".(int)($lastBatch+$i)])) { + $isCollection && self::isEmpty($_POST["artist".(int)($lastBatch+$i)])) { $_REQUEST["nextTrack"] -= $this->tracksPerPage; $focusTrack = $lastBatch+$i; break; } } - } else $_REQUEST["nextTrack"] = 1; - - echo "\n"; - echo "Insert/Delete Track:  \n"; - $size = $_REQUEST["coll"]?30:60; - if(!$focusTrack) - $focusTrack = $_REQUEST["nextTrack"]; - for($i=0; $i<$this->tracksPerPage; $i++) { - $trackNum = $_REQUEST["nextTrack"] + $i; - echo " \n"; + } else { + $_REQUEST["nextTrack"] = 1; } - echo "    \n"; + + $focusTrack = $focusTrack ? $focusTrack : $_REQUEST["nextTrack"]; + $this->emitTrackList($focusTrack, $isCollection); ?> -
Track $trackNum:"; - $this->skipVar("track".$trackNum); - if($_REQUEST["coll"]) { - echo "Artist:"; - $this->skipVar("artist".$trackNum); - } - echo "
- tracksPerPage);?>> + +
+ + + tracksPerPage);?>> +
+ noTables); + return UI::HTMLify($arg, $size, false); } public function findAlbum() { $this->searchByAlbumKey($_REQUEST["n"]); } + private function emitTrackUrl($trackInfo) { + $url = $trackInfo["url"]; + $link = $url == '' ? '' : "
"; + echo $link; + } + public function searchByAlbumKey($key=0) { $opened = 0; @@ -197,23 +201,20 @@ public function searchByAlbumKey($key=0) { $this->newEntity(Reviews::class)->viewReview2($this->searchText); // Emit Tracks - echo "\n
Track Listing
\n"; + echo "\n
Track Listing
\n"; // Handle collection tracks $albums = Engine::api(ILibrary::class)->search(ILibrary::COLL_KEY, 0, 200, $this->searchText); for($i = 0; $i < sizeof($albums); $i++) { if($i == 0) { - if($this->noTables) - // 3 20 32 - echo "
  # Artist               Track Name                      \n";
-                else
-                    echo "\n  \n";
+                echo "
 ArtistTrack Name
\n \n"; } - + + echo " "; + echo ""; echo "\n"; } if($i) @@ -245,26 +244,18 @@ public function searchByAlbumKey($key=0) { $mid = sizeof($tracks) / 2; for($i = 0; $i < $mid; $i++){ if(!$opened) { - if($this->noTables) - // 3 32 - echo "
  # Track Name                      \n";
-                    else
-                        echo "
 ArtistTrack Name
"; + echo $this->emitTrackUrl($albums[$i]); + // Number - if($this->noTables) - echo UI::HTMLifyNum($albums[$i]["seq"], 3, 1); - echo "
".$albums[$i]["seq"]."."; + echo "".$albums[$i]["seq"]."."; // Artist Name echo "session->getSessionID(). "\">"; echo $this->HTMLify($albums[$i]["artist"], 20), ""; - if(!$this->noTables) - echo "\n"; + echo "\n"; // Track Name echo "session->getSessionID(). "\">"; echo $this->HTMLify($albums[$i]["track"], 32). ""; - if(!$this->noTables) - echo "
\n"; - + echo "
\n"; $opened = 1; } // Number if($mid - $i < 1) - if($this->noTables) - echo UI::HTMLify(" ", 36, 1); - else - echo " "; + echo " "; else { - if($this->noTables) - echo UI::HTMLifyNum($tracks[$i]["seq"], 3, 1); - else - echo " "; + echo ""; } - - if($this->noTables) - echo UI::HTMLifyNum($tracks[$mid + $i]["seq"], 3, 1); - else - echo ""; + echo ""; echo "\n"; } @@ -298,8 +286,6 @@ public function searchByAlbumKey($key=0) { } public function doSearch() { - $this->checkBrowserCaps(); - if(array_key_exists('m', $_REQUEST)) $this->exactMatch = $_REQUEST['m']; if(array_key_exists('n', $_REQUEST)) @@ -322,23 +308,10 @@ public function legacySearchLandingPage() { // returns closing tag for output private function closeList() { - if($this->noTables) - $close = "\n"; - else $close = "
 
 
".$tracks[$i]["seq"]."."; + // left track column + echo "
"; + echo $this->emitTrackUrl($tracks[$i]); + echo "".$tracks[$i]["seq"]."."; // Name echo "session->getSessionID(). "\">"; echo $this->HTMLify($tracks[$i]["track"], 32), ""; - if(!$this->noTables) - echo "  ".$tracks[$mid + $i]["seq"]."."; + + // right track column + echo ""; + echo $this->emitTrackUrl($tracks[$mid + $i]); + echo "".$tracks[$mid + $i]["seq"]."."; // Name echo "session->getSessionID(). "\">"; echo $this->HTMLify($tracks[$mid + $i]["track"], 32), ""; - - if(!$this->noTables) - echo "
\n"; return $close; } - // CheckBrowserCaps - // - // Check browser's capabilities: - // noTables property set true if browser does not support tables - // - private function checkBrowserCaps() { - // For now, we naively assume all browsers support tables except Lynx. - $this->noTables = (substr($_SERVER["HTTP_USER_AGENT"], 0, 5) == "Lynx/"); - } - public function searchForm() { UI::emitJS('js/jquery.bahashchange.min.js'); UI::emitJS('js/search.library.js');