Merge branch 'saas' into saas-speedy
This commit is contained in:
commit
3e255bfd37
|
@ -24,12 +24,14 @@ require_once "FileIO.php";
|
|||
require_once "OsPath.php";
|
||||
require_once "Database.php";
|
||||
require_once "ProvisioningHelper.php";
|
||||
require_once "GoogleAnalytics.php";
|
||||
require_once "Timezone.php";
|
||||
require_once "Auth.php";
|
||||
require_once __DIR__.'/forms/helpers/ValidationTypes.php';
|
||||
require_once __DIR__.'/forms/helpers/CustomDecorators.php';
|
||||
require_once __DIR__.'/controllers/plugins/RabbitMqPlugin.php';
|
||||
require_once __DIR__.'/controllers/plugins/Maintenance.php';
|
||||
require_once __DIR__.'/controllers/plugins/ConversionTracking.php';
|
||||
require_once __DIR__.'/modules/rest/controllers/ShowImageController.php';
|
||||
require_once __DIR__.'/modules/rest/controllers/MediaController.php';
|
||||
|
||||
|
@ -37,7 +39,7 @@ require_once (APPLICATION_PATH."/logging/Logging.php");
|
|||
Logging::setLogPath('/var/log/airtime/zendphp.log');
|
||||
|
||||
// We need to manually route because we can't load Zend without the database being initialized first.
|
||||
if (array_key_exists("REQUEST_URI", $_SERVER) && (strpos($_SERVER["REQUEST_URI"], "/provisioning/create") !== false)) {
|
||||
if (array_key_exists("REQUEST_URI", $_SERVER) && (stripos($_SERVER["REQUEST_URI"], "/provisioning/create") !== false)) {
|
||||
$provisioningHelper = new ProvisioningHelper($CC_CONFIG["apiKey"][0]);
|
||||
$provisioningHelper->createAction();
|
||||
die();
|
||||
|
@ -52,6 +54,8 @@ Application_Model_Auth::pinSessionToClient(Zend_Auth::getInstance());
|
|||
|
||||
$front = Zend_Controller_Front::getInstance();
|
||||
$front->registerPlugin(new RabbitMqPlugin());
|
||||
$front->registerPlugin(new Zend_Controller_Plugin_ConversionTracking());
|
||||
$front->throwExceptions(false);
|
||||
|
||||
//localization configuration
|
||||
Application_Model_Locale::configureLocalization();
|
||||
|
|
|
@ -9,30 +9,76 @@ class Amazon_S3StorageBackend extends StorageBackend
|
|||
{
|
||||
|
||||
private $s3Client;
|
||||
|
||||
private $proxyHost;
|
||||
|
||||
public function Amazon_S3StorageBackend($securityCredentials)
|
||||
{
|
||||
$this->setBucket($securityCredentials['bucket']);
|
||||
$this->setAccessKey($securityCredentials['api_key']);
|
||||
$this->setSecretKey($securityCredentials['api_key_secret']);
|
||||
|
||||
$this->s3Client = S3Client::factory(array(
|
||||
$s3Options = array(
|
||||
'key' => $securityCredentials['api_key'],
|
||||
'secret' => $securityCredentials['api_key_secret'],
|
||||
'region' => $securityCredentials['region']
|
||||
));
|
||||
}
|
||||
);
|
||||
if (array_key_exists("proxy_host", $securityCredentials)) {
|
||||
$s3Options = array_merge($s3Options, array(
|
||||
//'base_url' => "http://" . $securityCredentials['proxy_host'],
|
||||
'base_url' => "http://s3.amazonaws.com",
|
||||
'scheme' => "http",
|
||||
//'force_path_style' => true,
|
||||
'signature' => 'v4'
|
||||
));
|
||||
$this->proxyHost = $securityCredentials['proxy_host'];
|
||||
}
|
||||
|
||||
$this->s3Client = S3Client::factory($s3Options);
|
||||
}
|
||||
|
||||
public function getAbsoluteFilePath($resourceId)
|
||||
{
|
||||
return $this->s3Client->getObjectUrl($this->getBucket(), $resourceId);
|
||||
}
|
||||
|
||||
public function getSignedURL($resourceId)
|
||||
/** Returns a signed download URL from Amazon S3, expiring in 60 minutes */
|
||||
public function getDownloadURLs($resourceId)
|
||||
{
|
||||
return $this->s3Client->getObjectUrl($this->getBucket(), $resourceId, '+60 minutes');
|
||||
$urls = array();
|
||||
|
||||
$signedS3Url = $this->s3Client->getObjectUrl($this->getBucket(), $resourceId, '+60 minutes');
|
||||
|
||||
|
||||
//If we're using the proxy cache, we need to modify the request URL after it has
|
||||
//been generated by the above. (The request signature must be for the amazonaws.com,
|
||||
//not our proxy, since the proxy translates the host back to amazonaws.com)
|
||||
if ($this->proxyHost) {
|
||||
$p = parse_url($signedS3Url);
|
||||
$p["host"] = $this->getBucket() . "." . $this->proxyHost;
|
||||
$p["scheme"] = "http";
|
||||
//If the path contains the bucket name (which is the case with HTTPS requests to Amazon),
|
||||
//we need to strip that part out, since we're forcing everything to HTTP. The Amazon S3
|
||||
//URL convention for HTTP is to prepend the bucket name to the hostname instead of having
|
||||
//it in the path.
|
||||
//eg. http://bucket.s3.amazonaws.com/ instead of https://s3.amazonaws.com/bucket/
|
||||
if (strpos($p["path"], $this->getBucket()) == 1) {
|
||||
$p["path"] = substr($p["path"], 1 + strlen($this->getBucket()));
|
||||
}
|
||||
$proxyUrl = $p["scheme"] . "://" . $p["host"] . $p["path"] . "?" . $p["query"];
|
||||
//Add this proxy cache URL to the list of download URLs.
|
||||
array_push($urls, $proxyUrl);
|
||||
}
|
||||
|
||||
//Add the direct S3 URL to the list (as a fallback)
|
||||
array_push($urls, $signedS3Url);
|
||||
|
||||
//http_build_url() would be nice to use but it requires pecl_http :-(
|
||||
|
||||
//Logging::info($url);
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
|
||||
public function deletePhysicalFile($resourceId)
|
||||
{
|
||||
$bucket = $this->getBucket();
|
||||
|
@ -53,14 +99,30 @@ class Amazon_S3StorageBackend extends StorageBackend
|
|||
// Records in the database will remain in case we have to restore the files.
|
||||
public function deleteAllCloudFileObjects()
|
||||
{
|
||||
$this->s3Client->deleteMatchingObjects(
|
||||
$bucket = $this->getBucket(),
|
||||
$prefix = $this->getFilePrefix());
|
||||
$bucket = $this->getBucket();
|
||||
$prefix = $this->getFilePrefix();
|
||||
|
||||
//Add a trailing slash in for safety
|
||||
//(so that deleting /13/413 doesn't delete /13/41313 !)
|
||||
$prefix = $prefix . "/";
|
||||
|
||||
//Do a bunch of safety checks to ensure we don't delete more than we intended.
|
||||
//An valid prefix is like "12/4312" for instance 4312.
|
||||
$slashPos = strpos($prefix, "/");
|
||||
if (($slashPos === FALSE) || //Slash must exist
|
||||
($slashPos != 2) || //Slash must be the third character
|
||||
(strlen($prefix) <= $slashPos) || //String must have something after the first slash
|
||||
(substr_count($prefix, "/") != 2)) //String must have two slashes
|
||||
{
|
||||
throw new Exception("Invalid file prefix in " . __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->s3Client->deleteMatchingObjects($bucket, $prefix);
|
||||
}
|
||||
|
||||
public function getFilePrefix()
|
||||
{
|
||||
$hostingId = Billing::getClientInstanceId();
|
||||
return substr($hostingId, -2)."/".$hostingId;
|
||||
$filePrefix = substr($hostingId, -2)."/".$hostingId;
|
||||
return $filePrefix;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class FileStorageBackend extends StorageBackend
|
|||
return $resourceId;
|
||||
}
|
||||
|
||||
public function getSignedURL($resourceId)
|
||||
public function getDownloadURLs($resourceId)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -38,9 +38,9 @@ class ProxyStorageBackend extends StorageBackend
|
|||
return $this->storageBackend->getAbsoluteFilePath($resourceId);
|
||||
}
|
||||
|
||||
public function getSignedURL($resourceId)
|
||||
public function getDownloadURLs($resourceId)
|
||||
{
|
||||
return $this->storageBackend->getSignedURL($resourceId);
|
||||
return $this->storageBackend->getDownloadURLs($resourceId);
|
||||
}
|
||||
|
||||
public function deletePhysicalFile($resourceId)
|
||||
|
|
|
@ -15,7 +15,7 @@ abstract class StorageBackend
|
|||
|
||||
/** Returns the file object's signed URL. The URL must be signed since they
|
||||
* privately stored on the storage backend. */
|
||||
abstract public function getSignedURL($resourceId);
|
||||
abstract public function getDownloadURLs($resourceId);
|
||||
|
||||
/** Deletes the file from the storage backend. */
|
||||
abstract public function deletePhysicalFile($resourceId);
|
||||
|
|
|
@ -10,7 +10,7 @@ class Application_Common_FileIO
|
|||
*
|
||||
* This HTTP_RANGE compatible read file function is necessary for allowing streaming media to be skipped around in.
|
||||
*
|
||||
* @param string $filePath - the full filepath pointing to the location of the file
|
||||
* @param string $filePath - the full filepath or URL pointing to the location of the file
|
||||
* @param string $mimeType - the file's mime type. Defaults to 'audio/mp3'
|
||||
* @param integer $size - the file size, in bytes
|
||||
* @return void
|
||||
|
@ -22,8 +22,7 @@ class Application_Common_FileIO
|
|||
{
|
||||
$fm = @fopen($filePath, 'rb');
|
||||
if (!$fm) {
|
||||
header ("HTTP/1.1 505 Internal server error");
|
||||
return;
|
||||
throw new FileNotFoundException($filePath);
|
||||
}
|
||||
|
||||
//Note that $size is allowed to be zero. If that's the case, it means we don't
|
||||
|
@ -36,6 +35,8 @@ class Application_Common_FileIO
|
|||
$begin = 0;
|
||||
$end = $size - 1;
|
||||
|
||||
ob_start(); //Must start a buffer here for these header() functions
|
||||
|
||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
|
||||
$begin = intval($matches[1]);
|
||||
|
@ -51,6 +52,7 @@ class Application_Common_FileIO
|
|||
header('HTTP/1.1 200 OK');
|
||||
}
|
||||
header("Content-Type: $mimeType");
|
||||
header("Content-Transfer-Encoding: binary");
|
||||
header('Cache-Control: public, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
header('Accept-Ranges: bytes');
|
||||
|
@ -60,12 +62,13 @@ class Application_Common_FileIO
|
|||
header("Content-Range: bytes $begin-$end/$size");
|
||||
}
|
||||
}
|
||||
header("Content-Transfer-Encoding: binary");
|
||||
|
||||
//We can have multiple levels of output buffering. Need to
|
||||
//keep looping until all have been disabled!!!
|
||||
//http://www.php.net/manual/en/function.ob-end-flush.php
|
||||
while (@ob_end_flush());
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_flush();
|
||||
}
|
||||
|
||||
// NOTE: We can't use fseek here because it does not work with streams
|
||||
// (a.k.a. Files stored in the cloud)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
class Application_Common_GoogleAnalytics
|
||||
{
|
||||
|
||||
/** Returns a string containing the JavaScript code to pass some billing account info
|
||||
* into Google Tag Manager / Google Analytics, so we can track things like the plan type.
|
||||
*/
|
||||
public static function generateGoogleTagManagerDataLayerJavaScript()
|
||||
{
|
||||
$code = "";
|
||||
|
||||
try {
|
||||
$clientId = Application_Model_Preference::GetClientId();
|
||||
|
||||
$plan = Application_Model_Preference::GetPlanLevel();
|
||||
$isTrial = ($plan == "trial");
|
||||
|
||||
//Figure out how long the customer has been around using a mega hack.
|
||||
//(I'm avoiding another round trip to WHMCS for now...)
|
||||
//We calculate it based on the trial end date...
|
||||
$trialEndDateStr = Application_Model_Preference::GetTrialEndingDate();
|
||||
if ($trialEndDateStr == '') {
|
||||
$accountDuration = 0;
|
||||
} else {
|
||||
$today = new DateTime();
|
||||
$trialEndDate = new DateTime($trialEndDateStr);
|
||||
$trialDuration = new DateInterval("P30D"); //30 day trial duration
|
||||
$accountCreationDate = $trialEndDate->sub($trialDuration);
|
||||
$interval = $today->diff($accountCreationDate);
|
||||
$accountDuration = $interval->days;
|
||||
}
|
||||
|
||||
$code = "$( document ).ready(function() {
|
||||
dataLayer.push({
|
||||
'UserID': '" . $clientId . "',
|
||||
'Customer': 'Customer',
|
||||
'PlanType': '" . $plan . "',
|
||||
'Trial': '" . $isTrial . "',
|
||||
'AccountDuration': '" . strval($accountDuration) . "'
|
||||
});
|
||||
});";
|
||||
//No longer sending these variables because we used to make a query to WHMCS
|
||||
//to fetch them, which was slow.
|
||||
// 'ZipCode': '" . $postcode . "',
|
||||
// 'Country': '" . $country . "',
|
||||
|
||||
} catch (Exception $e) {
|
||||
Logging::error($e);
|
||||
return "";
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/** Generate the JavaScript snippet that logs a trial to paid conversion */
|
||||
public static function generateConversionTrackingJavaScript()
|
||||
{
|
||||
$newPlan = Application_Model_Preference::GetPlanLevel();
|
||||
$oldPlan = Application_Model_Preference::GetOldPlanLevel();
|
||||
|
||||
$code = "dataLayer.push({'event': 'Conversion',
|
||||
'Conversion': 'Trial to Paid',
|
||||
'Old Plan' : '$oldPlan',
|
||||
'New Plan' : '$newPlan'});";
|
||||
return $code;
|
||||
}
|
||||
|
||||
/** Return true if the user used to be on a trial plan and was just converted to a paid plan. */
|
||||
public static function didPaidConversionOccur($request)
|
||||
{
|
||||
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
|
||||
if ($userInfo) {
|
||||
$user = new Application_Model_User($userInfo->id);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldPlan = Application_Model_Preference::GetOldPlanLevel();
|
||||
|
||||
if ($user->isSuperAdmin() &&
|
||||
!$user->isSourcefabricAdmin() &&
|
||||
$request->getControllerKey() !== "thank-you")
|
||||
{
|
||||
//Only tracking trial->paid conversions for now.
|
||||
if ($oldPlan == "trial")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ class Application_Common_HTTPHelper
|
|||
if (empty($baseDir)) {
|
||||
$baseDir = "/";
|
||||
}
|
||||
if ($baseDir[0] != "") {
|
||||
if ($baseDir[0] != "/") {
|
||||
$baseDir = "/" . $baseDir;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ class ProvisioningHelper
|
|||
// Parameter values
|
||||
private $dbuser, $dbpass, $dbname, $dbhost, $dbowner, $apikey;
|
||||
private $instanceId;
|
||||
private $station_name, $description;
|
||||
|
||||
public function __construct($apikey)
|
||||
{
|
||||
|
@ -34,34 +35,37 @@ class ProvisioningHelper
|
|||
try {
|
||||
|
||||
$this->parsePostParams();
|
||||
|
||||
|
||||
//For security, the Airtime Pro provisioning system creates the database for the user.
|
||||
$this->setNewDatabaseConnection();
|
||||
if ($this->dbhost && !empty($this->dbhost)) {
|
||||
$this->setNewDatabaseConnection();
|
||||
|
||||
//if ($this->checkDatabaseExists()) {
|
||||
// throw new Exception("ERROR: Airtime database already exists");
|
||||
//}
|
||||
//if ($this->checkDatabaseExists()) {
|
||||
// throw new Exception("ERROR: Airtime database already exists");
|
||||
//}
|
||||
|
||||
if (!$this->checkDatabaseExists()) {
|
||||
throw new Exception("ERROR: $this->dbname database does not exist.");
|
||||
}
|
||||
if (!$this->checkDatabaseExists()) {
|
||||
throw new Exception("ERROR: $this->dbname database does not exist.");
|
||||
}
|
||||
|
||||
//We really want to do this check because all the Propel-generated SQL starts with "DROP TABLE IF EXISTS".
|
||||
//If we don't check, then a second call to this API endpoint would wipe all the tables!
|
||||
if ($this->checkTablesExist()) {
|
||||
throw new Exception("ERROR: airtime tables already exists");
|
||||
//We really want to do this check because all the Propel-generated SQL starts with "DROP TABLE IF EXISTS".
|
||||
//If we don't check, then a second call to this API endpoint would wipe all the tables!
|
||||
if ($this->checkTablesExist()) {
|
||||
throw new Exception("ERROR: airtime tables already exists");
|
||||
}
|
||||
|
||||
$this->createDatabaseTables();
|
||||
$this->initializeMusicDirsTable($this->instanceId);
|
||||
}
|
||||
|
||||
//$this->createDatabase();
|
||||
|
||||
//All we need to do is create the database tables.
|
||||
|
||||
$this->createDatabaseTables();
|
||||
$this->initializeMusicDirsTable($this->instanceId);
|
||||
$this->initializePrefs();
|
||||
} catch (Exception $e) {
|
||||
http_response_code(400);
|
||||
Logging::error($e->getMessage()
|
||||
);
|
||||
Logging::error($e->getMessage());
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
|
@ -94,6 +98,7 @@ class ProvisioningHelper
|
|||
// Result is either boolean FALSE (no table found) or PDOStatement Object (table found)
|
||||
return $result !== FALSE;
|
||||
}
|
||||
|
||||
private function parsePostParams()
|
||||
{
|
||||
$this->dbuser = $_POST['dbuser'];
|
||||
|
@ -102,6 +107,9 @@ class ProvisioningHelper
|
|||
$this->dbhost = $_POST['dbhost'];
|
||||
$this->dbowner = $_POST['dbowner'];
|
||||
$this->instanceId = $_POST['instanceid'];
|
||||
|
||||
$this->station_name = $_POST['station_name'];
|
||||
$this->description = $_POST['description'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,9 +119,9 @@ class ProvisioningHelper
|
|||
private function setNewDatabaseConnection()
|
||||
{
|
||||
self::$dbh = new PDO("pgsql:host=" . $this->dbhost
|
||||
. ";dbname=" . $this->dbname
|
||||
. ";port=5432" . ";user=" . $this->dbuser
|
||||
. ";password=" . $this->dbpass);
|
||||
. ";dbname=" . $this->dbname
|
||||
. ";port=5432" . ";user=" . $this->dbuser
|
||||
. ";password=" . $this->dbpass);
|
||||
//Turn on PDO exceptions because they're off by default.
|
||||
//self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$err = self::$dbh->errorInfo();
|
||||
|
@ -130,8 +138,8 @@ class ProvisioningHelper
|
|||
{
|
||||
Logging::info("Creating database...");
|
||||
$statement = self::$dbh->prepare("CREATE DATABASE " . pg_escape_string($this->dbname)
|
||||
. " WITH ENCODING 'UTF8' TEMPLATE template0"
|
||||
. " OWNER " . pg_escape_string($this->dbowner));
|
||||
. " WITH ENCODING 'UTF8' TEMPLATE template0"
|
||||
. " OWNER " . pg_escape_string($this->dbowner));
|
||||
if (!$statement->execute()) {
|
||||
throw new Exception("ERROR: Failed to create Airtime database");
|
||||
}
|
||||
|
@ -182,5 +190,16 @@ class ProvisioningHelper
|
|||
$musicDir->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize preference values passed from the dashboard (if any exist)
|
||||
*/
|
||||
private function initializePrefs() {
|
||||
if ($this->station_name) {
|
||||
Application_Model_Preference::SetStationName($this->station_name);
|
||||
}
|
||||
if ($this->description) {
|
||||
Application_Model_Preference::SetStationDescription($this->description);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
|
|||
->add(new Zend_Acl_Resource('rest:media'))
|
||||
->add(new Zend_Acl_Resource('rest:show-image'))
|
||||
->add(new Zend_Acl_Resource('billing'))
|
||||
->add(new Zend_Acl_Resource('thank-you'))
|
||||
->add(new Zend_Acl_Resource('provisioning'));
|
||||
|
||||
/** Creating permissions */
|
||||
|
@ -69,6 +70,7 @@ $ccAcl->allow('G', 'index')
|
|||
->allow('A', 'user')
|
||||
->allow('A', 'systemstatus')
|
||||
->allow('A', 'preference')
|
||||
->allow('S', 'thank-you')
|
||||
->allow('S', 'billing');
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,13 @@ class Config {
|
|||
$CC_CONFIG['dev_env'] = 'production';
|
||||
}
|
||||
|
||||
//Backported static_base_dir default value into saas for now.
|
||||
if (array_key_exists('static_base_dir', $values['general'])) {
|
||||
$CC_CONFIG['staticBaseDir'] = $values['general']['static_base_dir'];
|
||||
} else {
|
||||
$CC_CONFIG['staticBaseDir'] = '/';
|
||||
}
|
||||
|
||||
// Parse separate conf file for cloud storage values
|
||||
$cloudStorageConfig = "/etc/airtime-saas/".$CC_CONFIG['dev_env']."/cloud_storage.conf";
|
||||
if (!file_exists($cloudStorageConfig)) {
|
||||
|
|
|
@ -1073,7 +1073,9 @@ class ApiController extends Zend_Controller_Action
|
|||
$dir->getId(),$all=false);
|
||||
foreach ($files as $f) {
|
||||
// if the file is from this mount
|
||||
if (substr($f->getFilePath(), 0, strlen($rd)) === $rd) {
|
||||
$filePaths = $f->getFilePaths();
|
||||
$filePath = $filePaths[0];
|
||||
if (substr($filePath, 0, strlen($rd)) === $rd) {
|
||||
$f->delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
<?php
|
||||
class ErrorController extends Zend_Controller_Action {
|
||||
|
||||
class ErrorController extends Zend_Controller_Action
|
||||
{
|
||||
|
||||
public function errorAction()
|
||||
public function init()
|
||||
{
|
||||
//The default layout includes the Dashboard header, which may contain private information.
|
||||
//We cannot show that.
|
||||
$this->view->layout()->disableLayout();
|
||||
$this->setupCSS();
|
||||
|
||||
}
|
||||
|
||||
public function errorAction() {
|
||||
$errors = $this->_getParam('error_handler');
|
||||
|
||||
switch ($errors->type) {
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
|
||||
if ($errors) {
|
||||
// log error message and stack trace
|
||||
Logging::error($errors->exception->getMessage());
|
||||
Logging::error($errors->exception->getTraceAsString());
|
||||
|
||||
// 404 error -- controller or action not found
|
||||
$this->getResponse()->setHttpResponseCode(404);
|
||||
$this->view->message = _('Page not found');
|
||||
break;
|
||||
default:
|
||||
// application error
|
||||
$this->getResponse()->setHttpResponseCode(500);
|
||||
$this->view->message = _('Application error');
|
||||
break;
|
||||
switch ($errors->type) {
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE :
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER :
|
||||
$this->error404Action();
|
||||
break;
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION :
|
||||
$this->error400Action();
|
||||
break;
|
||||
default :
|
||||
$this->error500Action();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$exceptions = $this->_getAllParams();
|
||||
Logging::error($exceptions);
|
||||
$this->error500Action();
|
||||
return;
|
||||
}
|
||||
|
||||
// Log exception, if logger available
|
||||
|
@ -33,11 +47,17 @@ class ErrorController extends Zend_Controller_Action
|
|||
$this->view->exception = $errors->exception;
|
||||
}
|
||||
|
||||
$this->view->request = $errors->request;
|
||||
$this->view->request = $errors->request;
|
||||
}
|
||||
|
||||
public function getLog()
|
||||
private function setupCSS()
|
||||
{
|
||||
$CC_CONFIG = Config::getConfig();
|
||||
$staticBaseDir = Application_Common_OsPath::formatDirectoryWithDirectorySeparators($CC_CONFIG['staticBaseDir']);
|
||||
$this->view->headLink()->appendStylesheet($staticBaseDir . 'css/styles.css?' . $CC_CONFIG['airtime_version']);
|
||||
}
|
||||
|
||||
public function getLog() {
|
||||
$bootstrap = $this->getInvokeArg('bootstrap');
|
||||
if (!$bootstrap->hasPluginResource('Log')) {
|
||||
return false;
|
||||
|
@ -47,9 +67,43 @@ class ErrorController extends Zend_Controller_Action
|
|||
return $log;
|
||||
}
|
||||
|
||||
public function deniedAction()
|
||||
{
|
||||
// action body
|
||||
/**
|
||||
* 404 error - route or controller
|
||||
*/
|
||||
public function error404Action() {
|
||||
$this->_helper->viewRenderer('error-404');
|
||||
$this->getResponse()->setHttpResponseCode(404);
|
||||
$this->view->message = _('Page not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 400 error - no such action
|
||||
*/
|
||||
public function error400Action() {
|
||||
$this->_helper->viewRenderer('error-400');
|
||||
$this->getResponse()->setHttpResponseCode(400);
|
||||
$this->view->message = _('The requested action is not supported.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 403 error - permission denied
|
||||
*/
|
||||
public function error403Action() {
|
||||
|
||||
$this->_helper->viewRenderer('error-403');
|
||||
$this->getResponse()->setHttpResponseCode(403);
|
||||
$this->view->message = _('You do not have permission to access this resource.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 500 error - internal server error
|
||||
*/
|
||||
public function error500Action() {
|
||||
|
||||
$this->_helper->viewRenderer('error-500');
|
||||
|
||||
$this->getResponse()->setHttpResponseCode(500);
|
||||
$this->view->message = _('An internal application error has occurred.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -356,6 +356,8 @@ class LibraryController extends Zend_Controller_Action
|
|||
$res = $file->delete();
|
||||
} catch (FileNoPermissionException $e) {
|
||||
$message = $noPermissionMsg;
|
||||
} catch (DeleteScheduledFileException $e) {
|
||||
$message = _("Could not delete file because it is scheduled in the future.");
|
||||
} catch (Exception $e) {
|
||||
//could throw a scheduled in future exception.
|
||||
$message = _("Could not delete file(s).");
|
||||
|
|
|
@ -18,6 +18,40 @@ class ProvisioningController extends Zend_Controller_Action
|
|||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Endpoint to change Airtime preferences remotely.
|
||||
* Mainly for use with the dashboard right now.
|
||||
*/
|
||||
public function changeAction() {
|
||||
$this->view->layout()->disableLayout();
|
||||
$this->_helper->viewRenderer->setNoRender(true);
|
||||
|
||||
if (!RestAuth::verifyAuth(true, false, $this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// This is hacky and should be genericized
|
||||
if ($_POST['station_name']) {
|
||||
Application_Model_Preference::SetStationName($_POST['station_name']);
|
||||
}
|
||||
if ($_POST['description']) {
|
||||
Application_Model_Preference::SetStationDescription($_POST['description']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->getResponse()
|
||||
->setHttpResponseCode(400)
|
||||
->appendBody("ERROR: " . $e->getMessage());
|
||||
Logging::error($e->getMessage());
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getResponse()
|
||||
->setHttpResponseCode(200)
|
||||
->appendBody("OK");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the Airtime Pro station's files from Amazon S3
|
||||
*/
|
||||
|
@ -31,12 +65,12 @@ class ProvisioningController extends Zend_Controller_Action
|
|||
}
|
||||
|
||||
$CC_CONFIG = Config::getConfig();
|
||||
|
||||
|
||||
foreach ($CC_CONFIG["supportedStorageBackends"] as $storageBackend) {
|
||||
$proxyStorageBackend = new ProxyStorageBackend($storageBackend);
|
||||
$proxyStorageBackend->deleteAllCloudFileObjects();
|
||||
}
|
||||
|
||||
|
||||
$this->getResponse()
|
||||
->setHttpResponseCode(200)
|
||||
->appendBody("OK");
|
||||
|
|
|
@ -35,7 +35,7 @@ class ShowbuilderController extends Zend_Controller_Action
|
|||
$user = Application_Model_User::GetCurrentUser();
|
||||
$userType = $user->getType();
|
||||
$this->view->headScript()->appendScript("localStorage.setItem( 'user-type', '$userType' );");
|
||||
$this->view->headScript()->appendScript($this->generateGoogleTagManagerDataLayerJavaScript());
|
||||
$this->view->headScript()->appendScript(Application_Common_GoogleAnalytics::generateGoogleTagManagerDataLayerJavaScript());
|
||||
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/contextmenu/jquery.contextMenu.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/datatables/js/jquery.dataTables.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
|
@ -369,104 +369,5 @@ class ShowbuilderController extends Zend_Controller_Action
|
|||
throw new Exception("this controller is/was a no-op please fix your
|
||||
code");
|
||||
}
|
||||
|
||||
/** Returns a string containing the JavaScript code to pass some billing account info
|
||||
* into Google Tag Manager / Google Analytics, so we can track things like the plan type.
|
||||
*/
|
||||
private static function generateGoogleTagManagerDataLayerJavaScript()
|
||||
{
|
||||
$code = "";
|
||||
|
||||
try
|
||||
{
|
||||
$accessKey = $_SERVER["WHMCS_ACCESS_KEY"];
|
||||
$username = $_SERVER["WHMCS_USERNAME"];
|
||||
$password = $_SERVER["WHMCS_PASSWORD"];
|
||||
$url = "https://account.sourcefabric.com/includes/api.php?accesskey=" . $accessKey; # URL to WHMCS API file goes here
|
||||
|
||||
$postfields = array();
|
||||
$postfields["username"] = $username;
|
||||
$postfields["password"] = md5($password);
|
||||
$postfields["action"] = "getclientsdetails";
|
||||
$postfields["stats"] = true;
|
||||
$postfields["clientid"] = Application_Model_Preference::GetClientId();
|
||||
$postfields["responsetype"] = "json";
|
||||
|
||||
$query_string = "";
|
||||
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // WHMCS IP whitelist doesn't support IPv6
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5); //Aggressive 5 second timeout
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
$jsondata = curl_exec($ch);
|
||||
if (curl_error($ch)) {
|
||||
//die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch));
|
||||
throw new Exception("WHMCS server down or invalid request.");
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$arr = json_decode($jsondata); # Decode JSON String
|
||||
|
||||
if ($arr->result !== "success") {
|
||||
Logging::warn("WHMCS API call failed in " . __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $arr->client;
|
||||
$stats = $arr->stats;
|
||||
$currencyCode = $client->currency_code;
|
||||
//$incomeCents = NumberFormatter::parseCurrency($stats->income, $currencyCode);
|
||||
|
||||
$isTrial = true;
|
||||
if (strpos($stats->income, "0.00") === FALSE) {
|
||||
$isTrial = false;
|
||||
}
|
||||
/*
|
||||
if ($incomeCents > 0) {
|
||||
$isTrial = false;
|
||||
}*/
|
||||
$plan = Application_Model_Preference::GetPlanLevel();
|
||||
$country = $client->country;
|
||||
$postcode = $client->postcode;
|
||||
|
||||
//Figure out how long the customer has been around using a mega hack.
|
||||
//(I'm avoiding another round trip to WHMCS for now...)
|
||||
//We calculate it based on the trial end date...
|
||||
$trialEndDateStr = Application_Model_Preference::GetTrialEndingDate();
|
||||
if ($trialEndDateStr == '') {
|
||||
$accountDuration = 0;
|
||||
} else {
|
||||
$today = new DateTime();
|
||||
$trialEndDate = new DateTime($trialEndDateStr);
|
||||
$trialDuration = new DateInterval("P30D"); //30 day trial duration
|
||||
$accountCreationDate = $trialEndDate->sub($trialDuration);
|
||||
$interval = $today->diff($accountCreationDate);
|
||||
$accountDuration = $interval->days;
|
||||
}
|
||||
|
||||
$code = "$( document ).ready(function() {
|
||||
dataLayer.push({
|
||||
'ZipCode': '" . $postcode . "',
|
||||
'UserID': '" . $client->id . "',
|
||||
'Customer': 'Customer',
|
||||
'PlanType': '" . $plan . "',
|
||||
'Trial': '" . $isTrial . "',
|
||||
'Country': '" . $country . "',
|
||||
'AccountDuration': '" . strval($accountDuration) . "'
|
||||
});
|
||||
});";
|
||||
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
class ThankYouController extends Zend_Controller_Action
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
//Variable for the template (thank-you/index.phtml)
|
||||
$this->view->stationUrl = Application_Common_HTTPHelper::getStationUrl();
|
||||
$this->view->conversionUrl = Application_Common_HTTPHelper::getStationUrl() . 'thank-you/confirm-conversion';
|
||||
$this->view->gaEventTrackingJsCode = ""; //Google Analytics event tracking code that logs an event.
|
||||
|
||||
// Embed the Google Analytics conversion tracking code if the
|
||||
// user is a super admin and old plan level is set to trial.
|
||||
if (Application_Common_GoogleAnalytics::didPaidConversionOccur($this->getRequest())) {
|
||||
$this->view->gaEventTrackingJsCode = Application_Common_GoogleAnalytics::generateConversionTrackingJavaScript();
|
||||
}
|
||||
|
||||
$csrf_namespace = new Zend_Session_Namespace('csrf_namespace');
|
||||
$csrf_element = new Zend_Form_Element_Hidden('csrf');
|
||||
$csrf_element->setValue($csrf_namespace->authtoken)->setRequired('true')->removeDecorator('HtmlTag')->removeDecorator('Label');
|
||||
$csrf_form = new Zend_Form();
|
||||
$csrf_form->addElement($csrf_element);
|
||||
$this->view->form = $csrf_form;
|
||||
}
|
||||
|
||||
/** Confirm that a conversion was tracked. */
|
||||
public function confirmConversionAction()
|
||||
{
|
||||
$this->view->layout()->disableLayout();
|
||||
$this->_helper->viewRenderer->setNoRender(true);
|
||||
|
||||
$current_namespace = new Zend_Session_Namespace('csrf_namespace');
|
||||
$observed_csrf_token = $this->_getParam('csrf_token');
|
||||
$expected_csrf_token = $current_namespace->authtoken;
|
||||
|
||||
if($observed_csrf_token != $expected_csrf_token) {
|
||||
Logging::info("Invalid CSRF token");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getRequest()->isPost()) {
|
||||
Logging::info("Goal conversion from trial to paid.");
|
||||
// Clear old plan level so we prevent duplicate events.
|
||||
// This should only be called from AJAX. See thank-you/index.phtml
|
||||
Application_Model_Preference::ClearOldPlanLevel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,8 @@ class UpgradeController extends Zend_Controller_Action
|
|||
array_push($upgraders, new AirtimeUpgrader259());
|
||||
array_push($upgraders, new AirtimeUpgrader2510());
|
||||
array_push($upgraders, new AirtimeUpgrader2511());
|
||||
|
||||
array_push($upgraders, new AirtimeUpgrader2512());
|
||||
|
||||
$didWePerformAnUpgrade = false;
|
||||
try
|
||||
{
|
||||
|
|
|
@ -28,7 +28,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
|
|||
{
|
||||
$this->_errorPage = array('module' => 'default',
|
||||
'controller' => 'error',
|
||||
'action' => 'denied');
|
||||
'action' => 'error');
|
||||
|
||||
$this->_roleName = $roleName;
|
||||
|
||||
|
@ -111,7 +111,16 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
|
|||
$controller = strtolower($request->getControllerName());
|
||||
Application_Model_Auth::pinSessionToClient(Zend_Auth::getInstance());
|
||||
|
||||
if (in_array($controller, array("api", "auth", "locale", "upgrade", 'whmcs-login', "provisioning"))) {
|
||||
if (in_array($controller, array(
|
||||
"api",
|
||||
"auth",
|
||||
"error",
|
||||
"locale",
|
||||
"upgrade",
|
||||
'whmcs-login',
|
||||
"provisioning"
|
||||
)))
|
||||
{
|
||||
$this->setRoleName("G");
|
||||
} elseif (!Zend_Auth::getInstance()->hasIdentity()) {
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
class Zend_Controller_Plugin_ConversionTracking extends Zend_Controller_Plugin_Abstract
|
||||
{
|
||||
public function preDispatch(Zend_Controller_Request_Abstract $request)
|
||||
{
|
||||
//If user is a super admin and old plan level is set to trial....
|
||||
if (Application_Common_GoogleAnalytics::didPaidConversionOccur($request))
|
||||
{
|
||||
//Redirect to Thank you page, unless the request was already going there...
|
||||
if ($request->getControllerName() != 'thank-you')
|
||||
{
|
||||
$request->setModuleName('default')
|
||||
->setControllerName('thank-you')
|
||||
->setActionName('index')
|
||||
->setDispatched(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE cc_show ALTER COLUMN description TYPE varchar(8192);
|
||||
ALTER TABLE cc_show_instances ALTER COLUMN description TYPE varchar(8192);
|
|
@ -38,7 +38,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|||
|
||||
<?php echo $this->partial('partialviews/trialBox.phtml', array("is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?>
|
||||
<div id="Panel">
|
||||
<div class="logo"></div>
|
||||
<?php echo $this->versionNotify();
|
||||
$sss = $this->SourceSwitchStatus();
|
||||
$scs = $this->SourceConnectionStatus();
|
||||
|
@ -47,17 +46,21 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|||
"scheduled_play_switch"=>$sss['scheduled_play'])) ?>
|
||||
<?php $partial = array('menu.phtml', 'default');
|
||||
$this->navigation()->menu()->setPartial($partial); ?>
|
||||
<div class="personal-block solo">
|
||||
<ul>
|
||||
<li>
|
||||
<!-- <span class="trial-box-button"><a title="Billing" href=<?php echo $baseUrl . 'billing/upgrade'?>>Upgrade</a></span> -->
|
||||
<a id="current-user" href=<?php echo $baseUrl . "User/edit-user"?>><span class="name"><?php echo $this->escape($this->loggedInAs()); ?></span></a>
|
||||
| <a href=<?php echo $baseUrl . "Login/logout"?>><?php echo _("Logout")?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="nav">
|
||||
<div class="logo"></div>
|
||||
<div class="personal-block solo">
|
||||
<ol>
|
||||
<li>
|
||||
<!-- <span class="trial-box-button"><a title="Billing" href=<?php echo $baseUrl . 'billing/upgrade'?>>Upgrade</a></span> -->
|
||||
<a id="current-user" href=<?php echo $baseUrl . "User/edit-user"?>><span class="name"><?php echo $this->escape($this->loggedInAs()); ?></span></a>
|
||||
| <a href=<?php echo $baseUrl . "Login/logout"?>><?php echo _("Logout")?></a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<?php echo $this->navigation()->menu() ?>
|
||||
<?php echo $this->navigation()->menu() ?>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrapper" id="content"><?php echo $this->layout()->content ?></div>
|
||||
|
|
|
@ -138,7 +138,9 @@ class Logging {
|
|||
switch($err['type'])
|
||||
{
|
||||
case E_ERROR:
|
||||
case E_WARNING:
|
||||
case E_PARSE:
|
||||
case E_NOTICE:
|
||||
case E_CORE_ERROR:
|
||||
case E_CORE_WARNING:
|
||||
case E_COMPILE_ERROR:
|
||||
|
|
|
@ -326,6 +326,11 @@ class Application_Model_Preference
|
|||
return self::getValue("station_name");
|
||||
}
|
||||
|
||||
public static function SetStationName($station_name)
|
||||
{
|
||||
self::setValue("station_name", $station_name);
|
||||
}
|
||||
|
||||
public static function SetAutoUploadRecordedShowToSoundcloud($upload)
|
||||
{
|
||||
self::setValue("soundcloud_auto_upload_recorded_show", $upload);
|
||||
|
@ -819,7 +824,10 @@ class Application_Model_Preference
|
|||
|
||||
public static function SetPlanLevel($plan)
|
||||
{
|
||||
$oldPlanLevel = self::GetPlanLevel();
|
||||
self::setValue("plan_level", $plan);
|
||||
//We save the old plan level temporarily to facilitate conversion tracking
|
||||
self::setValue("old_plan_level", $oldPlanLevel);
|
||||
}
|
||||
|
||||
public static function GetPlanLevel()
|
||||
|
@ -832,6 +840,19 @@ class Application_Model_Preference
|
|||
return $plan;
|
||||
}
|
||||
|
||||
public static function GetOldPlanLevel()
|
||||
{
|
||||
$oldPlan = self::getValue("old_plan_level");
|
||||
return $oldPlan;
|
||||
}
|
||||
|
||||
/** Clearing the old plan level indicates a change in your plan has been tracked (Google Analytics) */
|
||||
public static function ClearOldPlanLevel()
|
||||
{
|
||||
self::setValue("old_plan_level", '');
|
||||
}
|
||||
|
||||
|
||||
public static function SetTrialEndingDate($date)
|
||||
{
|
||||
self::setValue("trial_end_date", $date);
|
||||
|
|
|
@ -138,8 +138,11 @@ SQL;
|
|||
if (isset($file_id)) {
|
||||
$file = Application_Model_StoredFile::RecallById($file_id);
|
||||
|
||||
if (isset($file) && file_exists($file->getFilePath())) {
|
||||
return $file;
|
||||
if (isset($file)) {
|
||||
$filePaths = $file->getFilePaths();
|
||||
if (file_exists($filePaths[0])) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -362,8 +362,9 @@ SQL;
|
|||
{
|
||||
$exists = false;
|
||||
try {
|
||||
$filePath = $this->getFilePath();
|
||||
$exists = (file_exists($this->getFilePath()) && !is_dir($filePath));
|
||||
$filePaths = $this->getFilePaths();
|
||||
$filePath = $filePaths[0];
|
||||
$exists = (file_exists($filePath) && !is_dir($filePath));
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -399,7 +400,14 @@ SQL;
|
|||
//Delete the physical file from either the local stor directory
|
||||
//or from the cloud
|
||||
if ($this->_file->getDbImportStatus() == CcFiles::IMPORT_STATUS_SUCCESS) {
|
||||
$this->_file->deletePhysicalFile();
|
||||
try {
|
||||
$this->_file->deletePhysicalFile();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
//Just log the exception and continue.
|
||||
Logging::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
//Update the user's disk usage
|
||||
|
@ -444,8 +452,6 @@ SQL;
|
|||
*/
|
||||
public function deleteByMediaMonitor($deleteFromPlaylist=false)
|
||||
{
|
||||
$filepath = $this->getFilePath();
|
||||
|
||||
if ($deleteFromPlaylist) {
|
||||
Application_Model_Playlist::DeleteFileFromAllPlaylists($this->getId());
|
||||
}
|
||||
|
@ -499,13 +505,13 @@ SQL;
|
|||
/**
|
||||
* Get the absolute filepath
|
||||
*
|
||||
* @return string
|
||||
* @return array of strings
|
||||
*/
|
||||
public function getFilePath()
|
||||
public function getFilePaths()
|
||||
{
|
||||
assert($this->_file);
|
||||
|
||||
return $this->_file->getURLForTrackPreviewOrDownload();
|
||||
return $this->_file->getURLsForTrackPreviewOrDownload();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -984,15 +990,19 @@ SQL;
|
|||
} else {
|
||||
Logging::info("Moving file $audio_file to $audio_stor");
|
||||
|
||||
//Ensure we have permissions to overwrite the file in stor, in case it already exists.
|
||||
if (file_exists($audio_stor)) {
|
||||
chmod($audio_stor, 0644);
|
||||
}
|
||||
|
||||
// Martin K.: changed to rename: Much less load + quicker since this is
|
||||
// an atomic operation
|
||||
if (@rename($audio_file, $audio_stor) === false) {
|
||||
if (rename($audio_file, $audio_stor) === false) {
|
||||
//something went wrong likely there wasn't enough space in .
|
||||
//the audio_stor to move the file too warn the user that .
|
||||
//the file wasn't uploaded and they should check if there .
|
||||
//is enough disk space .
|
||||
unlink($audio_file); //remove the file after failed rename
|
||||
//unlink($id_file); // Also remove the identifier file
|
||||
|
||||
throw new Exception("The file was not uploaded, this error can occur if the computer "
|
||||
. "hard drive does not have enough disk space or the stor "
|
||||
|
@ -1239,9 +1249,11 @@ SQL;
|
|||
$genre = $file->getDbGenre();
|
||||
$release = $file->getDbUtime();
|
||||
try {
|
||||
$filePaths = $this->getFilePaths();
|
||||
$filePath = $filePaths[0];
|
||||
$soundcloud = new Application_Model_Soundcloud();
|
||||
$soundcloud_res = $soundcloud->uploadTrack(
|
||||
$this->getFilePath(), $this->getName(), $description,
|
||||
$filePath, $this->getName(), $description,
|
||||
$tag, $release, $genre);
|
||||
$this->setSoundCloudFileId($soundcloud_res['id']);
|
||||
$this->setSoundCloudLinkToFile($soundcloud_res['permalink_url']);
|
||||
|
|
|
@ -69,6 +69,15 @@ class Application_Model_User
|
|||
return $result;
|
||||
}
|
||||
|
||||
public function isSourcefabricAdmin()
|
||||
{
|
||||
$username = $this->getLogin();
|
||||
if ($username == "sourcefabric_admin") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO : refactor code to only accept arrays for isUserType and
|
||||
// simplify code even further
|
||||
public function isUserType($type)
|
||||
|
|
|
@ -95,9 +95,10 @@ class CcFiles extends BaseCcFiles {
|
|||
|
||||
try {
|
||||
self::createAndImport($fileArray, $tempFilePath, $originalFilename);
|
||||
} catch (Exception $e)
|
||||
{
|
||||
@unlink($tempFilePath);
|
||||
} catch (Exception $e) {
|
||||
if (file_exists($tempFilePath)) {
|
||||
unlink($tempFilePath);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
@ -385,9 +386,9 @@ class CcFiles extends BaseCcFiles {
|
|||
/**
|
||||
* Returns the file's absolute file path stored on disk.
|
||||
*/
|
||||
public function getURLForTrackPreviewOrDownload()
|
||||
public function getURLsForTrackPreviewOrDownload()
|
||||
{
|
||||
return $this->getAbsoluteFilePath();
|
||||
return array($this->getAbsoluteFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,12 +27,12 @@ class CloudFile extends BaseCloudFile
|
|||
* requesting the file's object via this URL, it needs to be signed because
|
||||
* all objects stored on Amazon S3 are private.
|
||||
*/
|
||||
public function getURLForTrackPreviewOrDownload()
|
||||
public function getURLsForTrackPreviewOrDownload()
|
||||
{
|
||||
if ($this->proxyStorageBackend == null) {
|
||||
$this->proxyStorageBackend = new ProxyStorageBackend($this->getStorageBackend());
|
||||
}
|
||||
return $this->proxyStorageBackend->getSignedURL($this->getResourceId());
|
||||
return $this->proxyStorageBackend->getDownloadURLs($this->getResourceId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -124,6 +124,7 @@ class Rest_MediaController extends Zend_Rest_Controller
|
|||
catch (Exception $e) {
|
||||
$this->unknownErrorResponse();
|
||||
Logging::error($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,10 +55,11 @@ class Application_Service_MediaService
|
|||
if ($media == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
$filepath = $media->getFilePath();
|
||||
// Make sure we don't have some wrong result beecause of caching
|
||||
// Make sure we don't have some wrong result because of caching
|
||||
clearstatcache();
|
||||
|
||||
$filePath = "";
|
||||
|
||||
if ($media->getPropelOrm()->isValidPhysicalFile()) {
|
||||
$filename = $media->getPropelOrm()->getFilename();
|
||||
//Download user left clicks a track and selects Download.
|
||||
|
@ -71,13 +72,41 @@ class Application_Service_MediaService
|
|||
header('Content-Disposition: inline; filename="' . $filename . '"');
|
||||
}
|
||||
|
||||
$filepath = $media->getFilePath();
|
||||
$size= $media->getFileSize();
|
||||
$mimeType = $media->getPropelOrm()->getDbMime();
|
||||
Application_Common_FileIO::smartReadFile($filepath, $size, $mimeType);
|
||||
/*
|
||||
In this block of code below, we're getting the list of download URLs for a track
|
||||
and then streaming the file as the response. A file can be stored in more than one location,
|
||||
with the alternate locations used as a fallback, so that's why we're looping until we
|
||||
are able to actually send the file.
|
||||
|
||||
This mechanism is used to try fetching our file from our internal S3 caching proxy server first.
|
||||
If the file isn't found there (or the cache is down), then we attempt to download the file
|
||||
directly from Amazon S3. We do this to save bandwidth costs!
|
||||
*/
|
||||
|
||||
$filePaths = $media->getFilePaths();
|
||||
assert(is_array($filePaths));
|
||||
|
||||
do {
|
||||
//Read from $filePath and stream it to the browser.
|
||||
$filePath = array_shift($filePaths);
|
||||
try {
|
||||
$size= $media->getFileSize();
|
||||
$mimeType = $media->getPropelOrm()->getDbMime();
|
||||
Application_Common_FileIO::smartReadFile($filePath, $size, $mimeType);
|
||||
break; //Break out of the loop if we successfully read the file!
|
||||
} catch (FileNotFoundException $e) {
|
||||
//If we have no alternate filepaths left, then let the exception bubble up.
|
||||
if (sizeof($filePaths) == 0) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
//Retry with the next alternate filepath in the list
|
||||
} while (sizeof($filePaths) > 0);
|
||||
|
||||
exit;
|
||||
|
||||
} else {
|
||||
throw new FileNotFoundException();
|
||||
throw new FileNotFoundException($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,14 +38,17 @@ abstract class AirtimeUpgrader
|
|||
//create a temporary maintenance notification file
|
||||
//when this file is on the server, zend framework redirects all
|
||||
//requests to the maintenance page and sets a 503 response code
|
||||
/* DISABLED because this does not work correctly
|
||||
$this->maintenanceFile = isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."maintenance.txt" : "/tmp/maintenance.txt";
|
||||
$file = fopen($this->maintenanceFile, 'w');
|
||||
fclose($file);
|
||||
*/
|
||||
} else {
|
||||
//delete maintenance.txt to give users access back to Airtime
|
||||
/* DISABLED because this does not work correctly
|
||||
if ($this->maintenanceFile) {
|
||||
unlink($this->maintenanceFile);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,4 +384,56 @@ class AirtimeUpgrader2511 extends AirtimeUpgrader
|
|||
throw $e;
|
||||
}
|
||||
}
|
||||
public function downgrade() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class AirtimeUpgrader2512 extends AirtimeUpgrader
|
||||
{
|
||||
protected function getSupportedVersions() {
|
||||
return array (
|
||||
'2.5.10',
|
||||
'2.5.11'
|
||||
);
|
||||
}
|
||||
|
||||
public function getNewVersion() {
|
||||
return '2.5.12';
|
||||
}
|
||||
|
||||
public function upgrade($dir = __DIR__) {
|
||||
Cache::clear();
|
||||
assert($this->checkIfUpgradeSupported());
|
||||
|
||||
$newVersion = $this->getNewVersion();
|
||||
|
||||
try {
|
||||
$this->toggleMaintenanceScreen(true);
|
||||
Cache::clear();
|
||||
|
||||
// Begin upgrade
|
||||
$airtimeConf = isset($_SERVER['AIRTIME_CONF']) ? $_SERVER['AIRTIME_CONF'] : "/etc/airtime/airtime.conf";
|
||||
$values = parse_ini_file($airtimeConf, true);
|
||||
|
||||
$username = $values['database']['dbuser'];
|
||||
$password = $values['database']['dbpass'];
|
||||
$host = $values['database']['host'];
|
||||
$database = $values['database']['dbname'];
|
||||
|
||||
passthru("export PGPASSWORD=$password && psql -h $host -U $username -q -f $dir/upgrade_sql/airtime_"
|
||||
.$this->getNewVersion()."/upgrade.sql $database 2>&1 | grep -v -E \"will create implicit sequence|will create implicit index\"");
|
||||
|
||||
Application_Model_Preference::SetAirtimeVersion($newVersion);
|
||||
Cache::clear();
|
||||
|
||||
$this->toggleMaintenanceScreen(false);
|
||||
} catch(Exception $e) {
|
||||
$this->toggleMaintenanceScreen(false);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
public function downgrade() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN";
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title><?php echo _("An error has occurred.") ?></title>
|
||||
<?php echo $this->headLink(); ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-content" id="error-400">
|
||||
<h2><?php echo _("Bad Request!")?></h2>
|
||||
<p><?php echo _("The requested action is not supported!")?></p>
|
||||
<div class="button-bar">
|
||||
<a class="toggle-button" href="<?php echo $this->baseUrl('dashboard/help'); ?>"><?php echo _("Help") ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN";
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title><?php echo _("An error has occurred.") ?></title>
|
||||
<?php echo $this->headLink(); ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-content" id="error-403">
|
||||
<h2><?php echo _("Access Denied!")?></h2>
|
||||
<p><?php echo _("You do not have permission to access this page!")?></p>
|
||||
<div class="button-bar">
|
||||
<a class="toggle-button" href="<?php echo $this->baseUrl('dashboard/help'); ?>"><?php echo _("Help") ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN";
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title><?php echo _("An error has occurred.") ?></title>
|
||||
<?php echo $this->headLink(); ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-content" id="error-500">
|
||||
<h2><?php echo _("Oops!")?></h2>
|
||||
<p><?php echo _("Something went wrong!")?></p>
|
||||
<div class="button-bar">
|
||||
<a class="toggle-button" href="<?php echo $this->baseUrl('dashboard/help'); ?>"><?php echo _("Help") ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -3,7 +3,8 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title><?php echo _("Zend Framework Default Application") ?></title>
|
||||
<title><?php echo _("An error has occurred.") ?></title>
|
||||
<?php echo $this->headLink(); ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-content">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<ul id="nav">
|
||||
<ol id="navlist">
|
||||
<?php foreach ($this->container as $page) : ?>
|
||||
<?php if($this->navigation()->accept($page)) : ?>
|
||||
<li class="top <?php if($page->isActive(true)){echo 'active';} ?>">
|
||||
|
@ -29,4 +29,4 @@
|
|||
</li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</ol>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
<?php if ($this->gaEventTrackingJsCode != "") {
|
||||
echo($this->gaEventTrackingJsCode);
|
||||
?>
|
||||
jQuery.post("<?=$this->conversionUrl?>", { "csrf_token" : $("#csrf").attr('value')},
|
||||
function( data ) {});
|
||||
<?php }; //endif ?>
|
||||
});
|
||||
</script>
|
||||
<?php echo $this->form->getElement('csrf') ?>
|
||||
|
||||
<div class="ui-widget ui-widget-content block-shadow clearfix padded-strong thankyou-panel">
|
||||
<center>
|
||||
<div class="logobox" style="margin-left: 32px;"></div>
|
||||
<h2><?php echo _pro("Thank you!")?></h2>
|
||||
<h3><?php echo _pro("Your station has been upgraded successfully.")?></h3>
|
||||
<p><a href="<?=$this->stationUrl?>"><?php echo _pro("Return to Airtime")?></a></p>
|
||||
</center>
|
||||
</div>
|
|
@ -139,7 +139,7 @@
|
|||
<column name="name" phpName="DbName" type="VARCHAR" size="255" required="true" defaultValue=""/>
|
||||
<column name="url" phpName="DbUrl" type="VARCHAR" size="255" required="false" defaultValue=""/>
|
||||
<column name="genre" phpName="DbGenre" type="VARCHAR" size="255" required="false" defaultValue=""/>
|
||||
<column name="description" phpName="DbDescription" type="VARCHAR" size="512" required="false"/>
|
||||
<column name="description" phpName="DbDescription" type="VARCHAR" size="8192" required="false"/>
|
||||
<column name="color" phpName="DbColor" type="VARCHAR" size="6" required="false"/>
|
||||
<column name="background_color" phpName="DbBackgroundColor" type="VARCHAR" size="6" required="false"/>
|
||||
<column name="live_stream_using_airtime_auth" phpName="DbLiveStreamUsingAirtimeAuth" type="BOOLEAN" required="false" defaultValue="false"/>
|
||||
|
@ -156,7 +156,7 @@
|
|||
</table>
|
||||
<table name="cc_show_instances" phpName="CcShowInstances">
|
||||
<column name="id" phpName="DbId" type="INTEGER" primaryKey="true" autoIncrement="true" required="true"/>
|
||||
<column name="description" phpName="DbDescription" type="VARCHAR" size="512" required="false" defaultValue=""/>
|
||||
<column name="description" phpName="DbDescription" type="VARCHAR" size="8192" required="false" defaultValue=""/>
|
||||
<column name="starts" phpName="DbStarts" type="TIMESTAMP" required="true"/>
|
||||
<column name="ends" phpName="DbEnds" type="TIMESTAMP" required="true"/>
|
||||
<column name="show_id" phpName="DbShowId" type="INTEGER" required="true"/>
|
||||
|
|
|
@ -149,7 +149,7 @@ CREATE TABLE "cc_show"
|
|||
"name" VARCHAR(255) DEFAULT '' NOT NULL,
|
||||
"url" VARCHAR(255) DEFAULT '',
|
||||
"genre" VARCHAR(255) DEFAULT '',
|
||||
"description" VARCHAR(512),
|
||||
"description" VARCHAR(8192),
|
||||
"color" VARCHAR(6),
|
||||
"background_color" VARCHAR(6),
|
||||
"live_stream_using_airtime_auth" BOOLEAN DEFAULT 'f',
|
||||
|
@ -171,7 +171,7 @@ DROP TABLE IF EXISTS "cc_show_instances" CASCADE;
|
|||
CREATE TABLE "cc_show_instances"
|
||||
(
|
||||
"id" serial NOT NULL,
|
||||
"description" VARCHAR(512) DEFAULT '',
|
||||
"description" VARCHAR(8192) DEFAULT '',
|
||||
"starts" TIMESTAMP NOT NULL,
|
||||
"ends" TIMESTAMP NOT NULL,
|
||||
"show_id" INTEGER NOT NULL,
|
||||
|
|
|
@ -463,8 +463,10 @@ class AMQPConnection extends AbstractChannel
|
|||
{
|
||||
debug_msg("closing socket");
|
||||
}
|
||||
|
||||
@fclose($this->sock);
|
||||
|
||||
if (is_resource($this->sock)) {
|
||||
@fclose($this->sock);
|
||||
}
|
||||
$this->sock = NULL;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -300,13 +300,12 @@
|
|||
|
||||
|
||||
.personal-block.solo {
|
||||
position: absolute;
|
||||
right: 145px;
|
||||
top: 104px;
|
||||
width: auto;
|
||||
z-index: 1000;
|
||||
height:auto;
|
||||
margin:0;
|
||||
width: auto;
|
||||
height:auto;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
.personal-block.solo ol {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.time-info-block.pull-right {
|
||||
margin-right:0;
|
||||
|
|
|
@ -48,14 +48,12 @@ select {
|
|||
}
|
||||
|
||||
.logo {
|
||||
position:absolute;
|
||||
right:20px;
|
||||
top:104px;
|
||||
background:transparent url(images/airtime_logo.png) no-repeat 0 0;
|
||||
height:35px;
|
||||
width:66px;
|
||||
z-index:1000;
|
||||
display:block;
|
||||
background: transparent url(images/airtime_logo.png) no-repeat 0 0;
|
||||
height: 35px;
|
||||
width: 66px;
|
||||
float: right;
|
||||
padding: 0 5px 0 10px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
/* Version Notification Starts*/
|
||||
|
@ -286,8 +284,74 @@ select {
|
|||
background:url(images/masterpanel_spacer.png) no-repeat right 0;
|
||||
}
|
||||
.time-info-block {
|
||||
padding:0 14px 0 2px;
|
||||
min-width:105px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
#navlist {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#nav li.top {
|
||||
/*float: none;*/
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
.now-playing-block {
|
||||
width: 30%;
|
||||
}
|
||||
.show-block {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 920px) {
|
||||
.now-playing-block {
|
||||
width: 50%;
|
||||
}
|
||||
.show-block {
|
||||
display: none;
|
||||
}
|
||||
.personal-block.solo {
|
||||
right: 10px !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 810px) {
|
||||
.now-playing-block {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 863px) {
|
||||
#nav {
|
||||
height: inherit;
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 680px) {
|
||||
.now-playing-block {
|
||||
display: none;
|
||||
}
|
||||
#nav li.top {
|
||||
display: -webkit-flex;
|
||||
width: 110px;
|
||||
}
|
||||
.personal-block.solo {
|
||||
float: none;
|
||||
text-align: left;
|
||||
}
|
||||
.personal-block.solo ol {
|
||||
padding-left: 12px;
|
||||
}
|
||||
.logo {
|
||||
float: none;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 380px) {
|
||||
.time-info-block {
|
||||
display: none;
|
||||
}
|
||||
.on-air-block {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.time-info-block ul {
|
||||
margin:0;
|
||||
|
@ -899,20 +963,19 @@ input[type="checkbox"] {
|
|||
|
||||
/* Remove any visible csrf form token footprint */
|
||||
#csrf-label {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#csrf-element {
|
||||
height: 8px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/*
|
||||
#csrf-label .errors li, #csrf-element .errors li {
|
||||
margin: 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
.login_box {
|
||||
margin: 0 auto 0 auto;
|
||||
|
@ -1031,7 +1094,6 @@ input[type="checkbox"] {
|
|||
#pref_form p.description {
|
||||
color: #3b3b3b;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
dt.block-display, dd.block-display {
|
||||
|
@ -2193,7 +2255,7 @@ dd.radio-inline-list, .preferences dd.radio-inline-list, .stream-config dd.radio
|
|||
width: 98.5%;
|
||||
}
|
||||
|
||||
.preferences dd#SoundCloudTags-element.block-display .input_text_area {
|
||||
.preferences dd.block-display .input_text_area {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
|
@ -2202,14 +2264,10 @@ dd.radio-inline-list, .preferences dd.radio-inline-list, .stream-config dd.radio
|
|||
}
|
||||
|
||||
.preferences #logo-remove-btn {
|
||||
float: right;
|
||||
/*float: left;*/
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.preferences #Logo-img-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
#show_time_info {
|
||||
font-size:12px;
|
||||
height:30px;
|
||||
|
@ -2570,19 +2628,21 @@ dt.block-display.info-block {
|
|||
|
||||
/*---//////////////////// ERROR PAGE ////////////////////---*/
|
||||
|
||||
.error-content {
|
||||
background:url(images/404.png) no-repeat 0 0;
|
||||
width:300px;
|
||||
margin: 24px 15px;
|
||||
padding: 0px 10px 0 420px;
|
||||
.error-content {
|
||||
background:url(images/maintenance.png) no-repeat 0 0;
|
||||
width:360px;
|
||||
height:350px;
|
||||
margin:auto;
|
||||
margin-top:25px;
|
||||
padding:auto;
|
||||
}
|
||||
.error-content h2 {
|
||||
margin:0;
|
||||
padding:0 0 10px 0;
|
||||
padding:350px 0 10px 0;
|
||||
font-size:36px;
|
||||
font-weight:bold;
|
||||
color:#3e3e3e;
|
||||
text-align:left;
|
||||
text-align:center;
|
||||
letter-spacing:-.3px;
|
||||
text-shadow: rgba(248,248,248,.3) 0 1px 0, rgba(0,0,0,.8) 0 -1px 0;
|
||||
rgba(51,51,51,.9)
|
||||
|
@ -2590,12 +2650,14 @@ dt.block-display.info-block {
|
|||
.error-content p {
|
||||
color: #272727;
|
||||
font-size: 16px;
|
||||
text-align:center;
|
||||
margin: 0;
|
||||
padding:8px 2px;
|
||||
}
|
||||
.error-content .button-bar {
|
||||
.error-content .button-bar {
|
||||
margin-top:47px;
|
||||
padding-left:2px;
|
||||
text-align:center;
|
||||
}
|
||||
.error-content .toggle-button {
|
||||
border: 1px solid #434343;
|
||||
|
@ -3142,3 +3204,16 @@ dd .stream-status {
|
|||
}
|
||||
.quota-reached {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.thankyou-panel
|
||||
{
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.thankyou-panel h3
|
||||
{
|
||||
color: #222;
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ function closeAddShowForm(event) {
|
|||
|
||||
redrawAddShowForm($el, json.form);
|
||||
});
|
||||
|
||||
|
||||
makeAddShowButton();
|
||||
}
|
||||
|
||||
|
@ -742,7 +742,7 @@ function setAddShowEvents(form) {
|
|||
image = new FormData();
|
||||
image.append('file', $('#add_show_logo')[0].files[0]);
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: action,
|
||||
data: {format: "json", data: data, hosts: hosts, days: days},
|
||||
|
@ -784,6 +784,7 @@ function setAddShowEvents(form) {
|
|||
} else {
|
||||
redrawAddShowForm($addShowForm, json.newForm);
|
||||
scheduleRefetchEvents(json);
|
||||
$addShowForm.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,7 +21,9 @@ class AnalyzerPipeline:
|
|||
so that if it crashes, it does not kill the entire airtime_analyzer daemon and
|
||||
the failure to import can be reported back to the web application.
|
||||
"""
|
||||
|
||||
|
||||
IMPORT_STATUS_FAILED = 2
|
||||
|
||||
@staticmethod
|
||||
def run_analysis(queue, audio_file_path, import_directory, original_filename, storage_backend, file_prefix, cloud_storage_config):
|
||||
"""Analyze and import an audio file, and put all extracted metadata into queue.
|
||||
|
@ -86,12 +88,12 @@ class AnalyzerPipeline:
|
|||
queue.put(metadata)
|
||||
except UnplayableFileError as e:
|
||||
logging.exception(e)
|
||||
metadata["import_status"] = 2
|
||||
metadata["import_status"] = IMPORT_STATUS_FAILED
|
||||
metadata["reason"] = "The file could not be played."
|
||||
raise e
|
||||
except Exception as e:
|
||||
# Ensures the traceback for this child process gets written to our log files:
|
||||
logging.exception(e)
|
||||
logging.exception(e)
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -226,18 +226,19 @@ class MessageListener:
|
|||
else:
|
||||
raise Exception("Analyzer process terminated unexpectedly.")
|
||||
'''
|
||||
metadata = {}
|
||||
|
||||
q = Queue.Queue()
|
||||
try:
|
||||
AnalyzerPipeline.run_analysis(q, audio_file_path, import_directory, original_filename, storage_backend, file_prefix, cloud_storage_config)
|
||||
results = q.get()
|
||||
metadata = q.get()
|
||||
except Exception as e:
|
||||
logging.error("Analyzer pipeline exception", e)
|
||||
pass
|
||||
logging.error("Analyzer pipeline exception: %s" % str(e))
|
||||
metadata["import_status"] = AnalyzerPipeline.IMPORT_STATUS_FAILED
|
||||
|
||||
# Ensure our queue doesn't fill up and block due to unexpected behaviour. Defensive code.
|
||||
while not q.empty():
|
||||
q.get()
|
||||
|
||||
return results
|
||||
return metadata
|
||||
|
||||
|
|
|
@ -27,6 +27,6 @@ class PlayabilityAnalyzer(Analyzer):
|
|||
logging.warn("Failed to run: %s - %s. %s" % (command[0], e.strerror, "Do you have liquidsoap installed?"))
|
||||
except (subprocess.CalledProcessError, Exception) as e: # liquidsoap returned an error code
|
||||
logging.warn(e)
|
||||
raise UnplayableFileError
|
||||
raise UnplayableFileError()
|
||||
|
||||
return metadata
|
||||
|
|
|
@ -25,7 +25,7 @@ class PicklableHttpRequest:
|
|||
auth=requests.auth.HTTPBasicAuth(self.api_key, ''))
|
||||
|
||||
def process_http_requests(ipc_queue, http_retry_queue_path):
|
||||
''' Runs in a separate process and performs all the HTTP requests where we're
|
||||
''' Runs in a separate thread and performs all the HTTP requests where we're
|
||||
reporting extracted audio file metadata or errors back to the Airtime web application.
|
||||
|
||||
This process also checks every 5 seconds if there's failed HTTP requests that we
|
||||
|
@ -129,6 +129,7 @@ def send_http_request(picklable_request, retry_queue):
|
|||
retry_queue.append(picklable_request) # Retry it later
|
||||
except Exception as e:
|
||||
logging.error("HTTP request failed with unhandled exception. %s" % str(e))
|
||||
logging.error(traceback.format_exc())
|
||||
# Don't put the request into the retry queue, just give up on this one.
|
||||
# I'm doing this to protect against us getting some pathological request
|
||||
# that breaks our code. I don't want us pickling data that potentially
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
|
||||
post_file() {
|
||||
#kill process after 30 minutes (360*5=30 minutes)
|
||||
max_retry=360
|
||||
max_retry=5
|
||||
retry_count=0
|
||||
|
||||
file_path="${1}"
|
||||
# Give us write permissions on the file to prevent problems if the user
|
||||
# uploads a read-only file.
|
||||
chmod +w "${file_path}"
|
||||
|
||||
#We must remove commas because CURL can't upload files with commas in the name
|
||||
# http://curl.haxx.se/mail/archive-2009-07/0029.html
|
||||
stripped_file_path=${file_path//','/''}
|
||||
|
|
Loading…
Reference in New Issue