Merge branch 'saas-dev' into saas-stream-settings

This commit is contained in:
Duncan Sommerville 2015-07-08 12:47:24 -04:00
commit 6b9d9e8063
17 changed files with 186 additions and 141 deletions

View File

@ -146,9 +146,7 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
$baseUrl = Application_Common_OsPath::getBaseDir();
$view->headLink(array('rel' => 'icon',
'href' => $baseUrl . 'favicon.ico?' . $CC_CONFIG['airtime_version'],
'type' => 'image/x-icon'), 'PREPEND')
$view->headLink(array('rel' => 'icon', 'href' => $baseUrl . 'favicon.ico?' . $CC_CONFIG['airtime_version'], 'type' => 'image/x-icon'), 'PREPEND')
->appendStylesheet($baseUrl . 'css/bootstrap.css?' . $CC_CONFIG['airtime_version'])
->appendStylesheet($baseUrl . 'css/redmond/jquery-ui-1.8.8.custom.css?' . $CC_CONFIG['airtime_version'])
->appendStylesheet($baseUrl . 'css/pro_dropdown_3.css?' . $CC_CONFIG['airtime_version'])

View File

@ -2,6 +2,27 @@
class FileDataHelper {
public static function getAudioMimeTypeArray() {
return array(
"audio/ogg" => "ogg",
"application/ogg" => "ogg",
"audio/vorbis" => "ogg",
"audio/mp3" => "mp3",
"audio/mpeg" => "mp3",
"audio/mpeg3" => "mp3",
"audio/aac" => "aac",
"audio/aacp" => "aac",
"audio/mp4" => "mp4",
"audio/x-flac" => "flac",
"audio/wav" => "wav",
"audio/x-wav" => "wav",
"audio/mp2" => "mp2",
"audio/mp1" => "mp1",
"audio/x-ms-wma" => "wma",
"audio/basic" => "au",
);
}
/**
* We want to throw out invalid data and process the upload successfully
* at all costs, so check the data and sanitize it if necessary
@ -24,4 +45,24 @@ class FileDataHelper {
$data["bpm"] = intval($data["bpm"]);
}
}
/**
* Return a suitable extension for the given file
*
* @param string $mime
*
* @return string file extension with(!) a dot (for convenience)
*
* @throws Exception
*/
public static function getFileExtensionFromMime($mime)
{
$mime = trim(strtolower($mime));
try {
return ('.' . static::getAudioMimeTypeArray()[$mime]);
} catch (Exception $e) {
throw new Exception("Unknown file type: $mime");
}
}
}

View File

@ -45,8 +45,6 @@ class Application_Common_HTTPHelper
class ZendActionHttpException extends Exception {
private $_action;
/**
* @param Zend_Controller_Action $action
* @param int $statusCode
@ -58,8 +56,7 @@ class ZendActionHttpException extends Exception {
*/
public function __construct(Zend_Controller_Action $action, $statusCode, $message,
$code = 0, Exception $previous = null) {
$this->_action = $action;
Logging::info("Error in action " . $action->getRequest()->getActionName()
Logging::error("Error in action " . $action->getRequest()->getActionName()
. " with status code $statusCode: $message");
$action->getResponse()
->setHttpResponseCode($statusCode)
@ -67,8 +64,4 @@ class ZendActionHttpException extends Exception {
parent::__construct($message, $code, $previous);
}
public function getAction() {
return $this->_action;
}
}

View File

@ -98,7 +98,7 @@ class WidgetHelper
// javascript date formats so it's easier to sort the shows by day.
$result["weekDays"][$weekStartDateTime->format("Y-n-j")] = array();
$result["weekDays"][$weekStartDateTime->format("Y-n-j")]["dayOfMonth"] = $dateParse["day"];
$result["weekDays"][$weekStartDateTime->format("Y-n-j")]["dayOfWeek"] = strtoupper(date("D", $weekStartDateTime->getTimestamp()));
$result["weekDays"][$weekStartDateTime->format("Y-n-j")]["dayOfWeek"] = strtoupper(_(date("D", $weekStartDateTime->getTimestamp())));
// Shows scheduled for this day will get added to this array when
// we convert the show times to the client's local timezone in weekly-program.phtml
@ -127,6 +127,16 @@ class WidgetHelper
"ALL",
$showQueryDateRangeEnd->format("Y-m-d H:i:s"));
// Convert each start and end time string to DateTime objects
// so we can get a real timestamp. The timestamps will be used
// to convert into javascript Date objects.
foreach($shows as &$show) {
$dtStarts = new DateTime($show["starts"], new DateTimeZone("UTC"));
$show["starts_timestamp"] = $dtStarts->getTimestamp();
$dtEnds = new DateTime($show["ends"], new DateTimeZone("UTC"));
$show["ends_timestamp"] = $dtEnds->getTimestamp();
}
$result["shows"] = $shows;
// XSS exploit prevention

View File

@ -24,7 +24,8 @@ define('LICENSE_URL' , 'http://www.gnu.org/licenses/agpl-3.0-standalone.h
define('AIRTIME_COPYRIGHT_DATE' , '2010-2015');
define('AIRTIME_REST_VERSION' , '1.1');
define('AIRTIME_API_VERSION' , '1.1');
define('AIRTIME_CODE_VERSION' , '2.5.13');
// XXX: it's important that we upgrade this every time we add an upgrade!
define('AIRTIME_CODE_VERSION' , '2.5.14');
// Defaults
define('DEFAULT_LOGO_PLACEHOLDER', 1);

View File

@ -198,23 +198,10 @@ class AudiopreviewController extends Zend_Controller_Action
$elementMap['type'] = $track['type'];
if ($track['type'] == 0) {
$mime = $track['mime'];
//type is file
if (strtolower($mime) === 'audio/mp3') {
$elementMap['element_mp3'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/ogg') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/vorbis') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/mp4') {
$elementMap['element_m4a'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-flac') {
$elementMap['element_flac'] = $track['item_id'];
} else {
$mime = trim(strtolower($track['mime']));
try {
$elementMap['element_' . FileDataHelper::getAudioMimeTypeArray()[$mime]] = $track['item_id'];
} catch (Exception $e) {
throw new Exception("Unknown file type: $mime");
}
@ -288,22 +275,10 @@ class AudiopreviewController extends Zend_Controller_Action
$elementMap['type'] = $track['type'];
if ($track['type'] == 0) {
$mime = $track['mime'];
if (strtolower($mime) === 'audio/mp3') {
$elementMap['element_mp3'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/ogg') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/vorbis') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/mp4') {
$elementMap['element_m4a'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-flac') {
$elementMap['element_flac'] = $track['item_id'];
} else {
$mime = trim(strtolower($track['mime']));
try {
$elementMap['element_' . FileDataHelper::getAudioMimeTypeArray()[$mime]] = $track['item_id'];
} catch (Exception $e) {
throw new Exception("Unknown file type: $mime");
}

View File

@ -0,0 +1,4 @@
ALTER TABLE cc_pref ALTER COLUMN subjid SET NULL;
ALTER TABLE cc_pref ALTER COLUMN subjid SET DEFAULT NULL;
CREATE UNIQUE INDEX cc_pref_key_idx ON cc_pref (keystr) WHERE subjid IS NULL;
ANALYZE cc_pref;

View File

@ -98,7 +98,7 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
$showSourceMount = new Zend_Form_Element_Text('show_source_mount');
$showSourceMount->setAttrib('readonly', true)
->setLabel(_('Mount:'))
->setValue(isset($showSourceParams["mount"])?$showSourceParams["mount"]:"");
->setValue(isset($showSourceParams["path"])?$showSourceParams["path"]:"");
$this->addElement($showSourceMount);
// demo only code
@ -127,10 +127,10 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
'viewScript' => 'form/preferences_livestream.phtml',
'master_source_host' => isset($masterSourceParams["host"])?$masterSourceParams["host"]:"",
'master_source_port' => isset($masterSourceParams["port"])?$masterSourceParams["port"]:"",
'master_source_mount' => isset($masterSourceParams["mount"])?$masterSourceParams["mount"]:"",
'master_source_mount' => isset($masterSourceParams["path"])?$masterSourceParams["path"]:"",
'show_source_host' => isset($showSourceParams["host"])?$showSourceParams["host"]:"",
'show_source_port' => isset($showSourceParams["port"])?$showSourceParams["port"]:"",
'show_source_mount' => isset($showSourceParams["mount"])?$showSourceParams["mount"]:"",
'show_source_mount' => isset($showSourceParams["path"])?$showSourceParams["path"]:"",
'isDemo' => $isDemo,
)
)

View File

@ -27,17 +27,18 @@ class Application_Model_Preference
private static function setValue($key, $value, $isUserValue = false)
{
$cache = new Cache();
try {
$con = Propel::getConnection(CcPrefPeer::DATABASE_NAME);
$con->beginTransaction();
$con = Propel::getConnection(CcPrefPeer::DATABASE_NAME);
$con->beginTransaction();
try {
/* Comment this out while we reevaluate it in favor of a unique constraint
static::_lock($con); */
$userId = self::getUserId();
if ($isUserValue && is_null($userId))
if ($isUserValue && is_null($userId)) {
throw new Exception("User id can't be null for a user preference {$key}.");
}
//Check if key already exists
$sql = "SELECT COUNT(*) FROM cc_pref"
." WHERE keystr = :key";
@ -113,6 +114,28 @@ class Application_Model_Preference
$cache->store($key, $value, $isUserValue, $userId);
}
/**
* Given a PDO connection, lock the cc_pref table for the current transaction
*
* Creates a table level lock, which defaults to ACCESS EXCLUSIVE mode;
* see http://www.postgresql.org/docs/9.1/static/explicit-locking.html
*
* @param PDO $con
*/
private static function _lock($con) {
// If we're not in a transaction, a lock is pointless
if (!$con->inTransaction()) {
return;
}
// Don't specify NOWAIT here; we should block on obtaining this lock
// in case we're handling simultaneous requests.
// Locks only last until the end of the transaction, so we shouldn't have to
// worry about this causing any noticeable difference in request processing speed
$sql = "LOCK TABLE cc_pref";
$st = $con->prepare($sql);
$st->execute();
}
private static function getValue($key, $isUserValue = false)
{
$cache = new Cache();
@ -140,9 +163,9 @@ class Application_Model_Preference
$sql .= " AND subjid = :id";
$paramMap[':id'] = $userId;
}
$result = Application_Common_Database::prepareAndExecute($sql, $paramMap, Application_Common_Database::COLUMN);
//return an empty string if the result doesn't exist.
if ($result == 0) {
$res = "";

View File

@ -17,6 +17,7 @@ class Application_Model_StoredFile
{
/**
* @holds propel database object
* @var CcFiles
*/
private $_file;
@ -467,48 +468,6 @@ SQL;
$this->_file->save();
}
public function getRealFileExtension() {
$path = $this->_file->getDbFilepath();
$path_elements = explode('.', $path);
if (count($path_elements) < 2) {
return "";
} else {
return $path_elements[count($path_elements) - 1];
}
}
/**
* Return suitable extension.
*
* @return string
* file extension without a dot
*/
public function getFileExtension()
{
$possible_ext = $this->getRealFileExtension();
if ($possible_ext !== "") {
return $possible_ext;
}
// We fallback to guessing the extension from the mimetype if we
// cannot extract it from the file name
$mime = $this->_file->getDbMime();
if ($mime == "audio/ogg" || $mime == "application/ogg" || $mime == "audio/vorbis") {
return "ogg";
} elseif ($mime == "audio/mp3" || $mime == "audio/mpeg") {
return "mp3";
} elseif ($mime == "audio/x-flac") {
return "flac";
} elseif ($mime == "audio/mp4") {
return "mp4";
} else {
throw new Exception("Unknown $mime");
}
}
/**
* Get the absolute filepath
*
@ -568,7 +527,7 @@ SQL;
*/
public function getRelativeFileUrl($baseUrl)
{
return $baseUrl."api/get-media/file/".$this->getId().".".$this->getFileExtension();
return $baseUrl."api/get-media/file/".$this->getId();
}
public function getResourceId()

View File

@ -205,11 +205,6 @@ class CcFiles extends BaseCcFiles {
$cloudFile->save();
Application_Model_Preference::updateDiskUsage($fileSizeBytes);
$now = new DateTime("now", new DateTimeZone("UTC"));
$file->setDbMtime($now);
$file->save();
} else if ($file) {
// Since we check for this value when deleting files, set it first
@ -238,14 +233,13 @@ class CcFiles extends BaseCcFiles {
$file->setDbFilepath($filePathRelativeToStor);
}
}
$now = new DateTime("now", new DateTimeZone("UTC"));
$file->setDbMtime($now);
$file->save();
} else {
throw new FileNotFoundException();
}
$now = new DateTime("now", new DateTimeZone("UTC"));
$file->setDbMtime($now);
$file->save();
}
catch (FileNotFoundException $e)
{
@ -356,17 +350,27 @@ class CcFiles extends BaseCcFiles {
/**
*
* Strips out the private fields we do not want to send back in API responses
* @param $file string a CcFiles object
*
* @param CcFiles $file a CcFiles object
*
* @return array
*/
//TODO: rename this function?
public static function sanitizeResponse($file)
{
public static function sanitizeResponse($file) {
$response = $file->toArray(BasePeer::TYPE_FIELDNAME);
foreach (self::$privateFields as $key) {
unset($response[$key]);
}
$mime = $file->getDbMime();
if (!empty($mime)) {
// Get an extension based on the file's mime type and change the path to use this extension
$path = pathinfo($file->getDbFilepath());
$ext = FileDataHelper::getFileExtensionFromMime($mime);
$response["filepath"] = ($path["dirname"] . '/' . $path["filename"] . $ext);
}
return $response;
}

View File

@ -455,3 +455,21 @@ class AirtimeUpgrader2513 extends AirtimeUpgrader
return '2.5.13';
}
}
/**
* Class AirtimeUpgrader2514
*
* SAAS-923 - Add a partial constraint to cc_pref so that keystrings must be unique
*/
class AirtimeUpgrader2514 extends AirtimeUpgrader
{
protected function getSupportedSchemaVersions() {
return array (
'2.5.13'
);
}
public function getNewVersion() {
return '2.5.14';
}
}

View File

@ -40,8 +40,10 @@
// First we have to create a Date object out of the show time in UTC.
// Then we can format the string in the client's local timezone.
var start_date = new Date(value.starts + " UTC");
var end_date = new Date(value.ends + " UTC");
// NOTE: we have to multiply the timestamp by 1000 because in PHP
// the timestamps are in seconds and are in milliseconds in javascript.
var start_date = new Date(value.starts_timestamp*1000);
var end_date = new Date(value.ends_timestamp*1000);
// This variable is used to identify which schedule_data object (which day of the week)
// we should assign the show to.
@ -53,13 +55,17 @@
if ($window.schedule_data["weekDays"][format_start_date] !== undefined) {
$window.schedule_data["weekDays"][format_start_date]["shows"].push(
{
"show_start_hour": start_date.getHours().toString().paddingLeft("00")+":"+ start_date.getMinutes().toString().paddingLeft("00"),
"show_end_hour": end_date.getHours().toString().paddingLeft("00")+":"+ end_date.getMinutes().toString().paddingLeft("00"),
"show_start_hour": start_date.toLocaleTimeString([], { hour: 'numeric', minute : 'numeric' }),
"show_end_hour": end_date.toLocaleTimeString([], { hour: 'numeric', minute : 'numeric' }),
"name": value.name
});
}
});
$scope.weekDays = $window.schedule_data["weekDays"];
// Convert the object into an array to maintain the same order when we
// iterate over each weekday
$scope.weekDays = $.map($window.schedule_data["weekDays"], function(value, index) {
return [value];
});
$scope.isEmpty = function(obj) {
return obj.length == 0;

View File

@ -376,3 +376,11 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_descripti
INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_genre', '', 'string');
INSERT INTO cc_stream_setting (keyname, value, type) VALUES ('s4_channels', 'stereo', 'string');
-- added in 2.5.14 - this can't be set up in Propel's XML schema, so we need to do it here -- Duncan
ALTER TABLE cc_pref ALTER COLUMN subjid SET NULL;
ALTER TABLE cc_pref ALTER COLUMN subjid SET DEFAULT NULL;
CREATE UNIQUE INDEX cc_pref_key_idx ON cc_pref (keystr) WHERE subjid IS NULL;
ANALYZE cc_pref; -- this validates the new partial index
--end added in 2.5.14

View File

@ -123,7 +123,7 @@ background: rgba(53, 53, 53, 1.0);
.schedule_item div.time_grid {
/*padding-right: 10px;*/
width: 20%;
width: 30%;
font-weight: 300;
color: #AAAAAA;
display: inline-block;
@ -133,7 +133,7 @@ background: rgba(53, 53, 53, 1.0);
overflow: hidden;
text-overflow: ellipsis;
padding-left: 10px;
width: 77%;
width: 67%;
display:inline-block;
vertical-align: middle;
}
@ -182,11 +182,11 @@ background: rgba(53, 53, 53, 1.0);
@media (max-width: 630px) {
.schedule_item div.time_grid {
width: 25%;
width: 37%;
}
.schedule_item div.name_grid {
width: 70%;
width: 58%;
}
}
@ -197,31 +197,31 @@ background: rgba(53, 53, 53, 1.0);
}
.schedule_item div.time_grid {
width: 30%;
width: 35%;
}
.schedule_item div.name_grid {
width: 67%;
width: 60%;
}
}
@media (max-width: 500px) {
.schedule_item div.time_grid {
width: 35%;
width: 40%;
}
.schedule_item div.name_grid {
width: 62%;
width: 55%;
}
}
@media (max-width: 400px) {
.schedule_item div.time_grid {
width: 40%;
width: 90%;
}
.schedule_item div.name_grid {
width: 50%;
width: 90%;
}
}

View File

@ -97,7 +97,7 @@ body {
.schedule_item div.time_grid {
/*padding-right: 10px;*/
width: 20%;
width: 30%;
font-weight: 300;
color: #AAAAAA;
display: inline-block;
@ -107,7 +107,7 @@ body {
overflow: hidden;
text-overflow: ellipsis;
padding-left: 10px;
width: 77%;
width: 67%;
display:inline-block;
vertical-align: middle;
}
@ -132,11 +132,11 @@ body {
@media (max-width: 630px) {
.schedule_item div.time_grid {
width: 25%;
width: 37%;
}
.schedule_item div.name_grid {
width: 72%;
width: 58%;
}
}
@ -147,31 +147,31 @@ body {
}
.schedule_item div.time_grid {
width: 30%;
width: 35%;
}
.schedule_item div.name_grid {
width: 67%;
width: 60%;
}
}
@media (max-width: 500px) {
.schedule_item div.time_grid {
width: 35%;
width: 40%;
}
.schedule_item div.name_grid {
width: 62%;
width: 55%;
}
}
@media (max-width: 400px) {
.schedule_item div.time_grid {
width: 40%;
width: 90%;
}
.schedule_item div.name_grid {
width: 50%;
width: 90%;
}
}

View File

@ -786,6 +786,11 @@ function setAddShowEvents(form) {
scheduleRefetchEvents(json);
$addShowForm.hide();
}
/* CC-6062: Resize the window to avoid stretching the last column */
windowResize();
makeAddShowButton();
}
});
});