Merge branch 'saas-dev' into saas-dev-bandwidth-limits
This commit is contained in:
commit
34eb7d6e22
|
@ -96,6 +96,12 @@ class Config {
|
||||||
$CC_CONFIG['soundcloud-client-id'] = $globalAirtimeConfigValues['soundcloud']['soundcloud_client_id'];
|
$CC_CONFIG['soundcloud-client-id'] = $globalAirtimeConfigValues['soundcloud']['soundcloud_client_id'];
|
||||||
$CC_CONFIG['soundcloud-client-secret'] = $globalAirtimeConfigValues['soundcloud']['soundcloud_client_secret'];
|
$CC_CONFIG['soundcloud-client-secret'] = $globalAirtimeConfigValues['soundcloud']['soundcloud_client_secret'];
|
||||||
$CC_CONFIG['soundcloud-redirect-uri'] = $globalAirtimeConfigValues['soundcloud']['soundcloud_redirect_uri'];
|
$CC_CONFIG['soundcloud-redirect-uri'] = $globalAirtimeConfigValues['soundcloud']['soundcloud_redirect_uri'];
|
||||||
|
if (isset($globalAirtimeConfigValues['facebook']['facebook_app_id'])) {
|
||||||
|
$CC_CONFIG['facebook-app-id'] = $globalAirtimeConfigValues['facebook']['facebook_app_id'];
|
||||||
|
$CC_CONFIG['facebook-app-url'] = $globalAirtimeConfigValues['facebook']['facebook_app_url'];
|
||||||
|
$CC_CONFIG['facebook-app-api-key'] = $globalAirtimeConfigValues['facebook']['facebook_app_api_key'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(isset($values['demo']['demo'])){
|
if(isset($values['demo']['demo'])){
|
||||||
$CC_CONFIG['demo'] = $values['demo']['demo'];
|
$CC_CONFIG['demo'] = $values['demo']['demo'];
|
||||||
|
|
|
@ -48,6 +48,12 @@ $pages = array(
|
||||||
'module' => 'default',
|
'module' => 'default',
|
||||||
'controller' => 'embeddablewidgets',
|
'controller' => 'embeddablewidgets',
|
||||||
'action' => 'schedule',
|
'action' => 'schedule',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'label' => _('Facebook'),
|
||||||
|
'module' => 'default',
|
||||||
|
'controller' => 'embeddablewidgets',
|
||||||
|
'action' => 'facebook',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
|
@ -26,8 +26,8 @@ class EmbeddableWidgetsController extends Zend_Controller_Action
|
||||||
$this->view->player_form = $form;
|
$this->view->player_form = $form;
|
||||||
} else {
|
} else {
|
||||||
$this->view->player_error_msg = _("To configure and use the embeddable player you must:<br><br>
|
$this->view->player_error_msg = _("To configure and use the embeddable player you must:<br><br>
|
||||||
1. Enable at least one MP3, AAC, or OGG stream under System -> Streams<br>
|
1. Enable at least one MP3, AAC, or OGG stream under Settings -> Streams<br>
|
||||||
2. Enable the Public Airtime API under System -> Preferences");
|
2. Enable the Public Airtime API under Settings -> Preferences");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,83 @@ class EmbeddableWidgetsController extends Zend_Controller_Action
|
||||||
|
|
||||||
if (!$apiEnabled) {
|
if (!$apiEnabled) {
|
||||||
$this->view->weekly_schedule_error_msg = _("To use the embeddable weekly schedule widget you must:<br><br>
|
$this->view->weekly_schedule_error_msg = _("To use the embeddable weekly schedule widget you must:<br><br>
|
||||||
Enable the Public Airtime API under System -> Preferences");
|
Enable the Public Airtime API under Settings -> Preferences");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function facebookAction()
|
||||||
|
{
|
||||||
|
Zend_Layout::getMvcInstance()->assign('parent_page', 'Widgets');
|
||||||
|
|
||||||
|
$apiEnabled = Application_Model_Preference::GetAllow3rdPartyApi();
|
||||||
|
|
||||||
|
if (!$apiEnabled) {
|
||||||
|
$this->view->facebook_error_msg = _("To add the Radio Tab to your Facebook Page, you must first:<br><br>
|
||||||
|
Enable the Public Airtime API under Settings -> Preferences");
|
||||||
|
}
|
||||||
|
|
||||||
|
$CC_CONFIG = Config::getConfig();
|
||||||
|
$baseUrl = Application_Common_OsPath::getBaseDir();
|
||||||
|
|
||||||
|
$facebookAppId = $CC_CONFIG['facebook-app-id'];
|
||||||
|
$this->view->headScript()->appendScript("var FACEBOOK_APP_ID = " . json_encode($facebookAppId) . ";");
|
||||||
|
$this->view->headScript()->appendFile($baseUrl . 'js/airtime/common/facebook.js?' . $CC_CONFIG['airtime_version'], 'text/javascript');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Airtime makes an AJAX POST here after it successfully adds a tab to your Facebook page. */
|
||||||
|
public function facebookTabSuccessAction()
|
||||||
|
{
|
||||||
|
// disable the view and the layout
|
||||||
|
$this->view->layout()->disableLayout();
|
||||||
|
$this->_helper->viewRenderer->setNoRender(true);
|
||||||
|
|
||||||
|
//TODO: Get list of page IDs (deserialize)
|
||||||
|
|
||||||
|
$request = $this->getRequest();
|
||||||
|
if (!$request->isPost()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = $request->getPost();
|
||||||
|
$facebookPageIds = json_decode($values["pages"]);
|
||||||
|
|
||||||
|
$CC_CONFIG = Config::getConfig();
|
||||||
|
$facebookMicroserviceUrl = $CC_CONFIG['facebook-app-url'];
|
||||||
|
$facebookMicroserviceApiKey = $CC_CONFIG['facebook-app-api-key'];
|
||||||
|
|
||||||
|
//Post the page tab ID and station subdomain to the social microservice so that mapping can be saved
|
||||||
|
//in a database.
|
||||||
|
foreach ($facebookPageIds as $facebookPageId)
|
||||||
|
{
|
||||||
|
$postfields = array();
|
||||||
|
$postfields["facebookPageId"] = $facebookPageId;
|
||||||
|
$postfields["stationId"] = $CC_CONFIG['stationId'];
|
||||||
|
|
||||||
|
$query_string = "";
|
||||||
|
foreach ($postfields as $k => $v) $query_string .= "$k=".urlencode($v)."&";
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $facebookMicroserviceUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
|
||||||
|
curl_setopt($ch, CURLOPT_USERPWD, ":$facebookMicroserviceApiKey");
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||||
|
|
||||||
|
$jsondata = curl_exec($ch);
|
||||||
|
if (curl_error($ch)) {
|
||||||
|
throw new Exception("Failed to reach server in " . __FUNCTION__ . ": "
|
||||||
|
. curl_errno($ch) . ' - ' . curl_error($ch) . ' - ' . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
|
||||||
|
}
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//$arr = json_decode($jsondata, true); # Decode JSON String
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,9 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<div id="fb-root"></div>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1572,6 +1572,9 @@ class Application_Model_Preference
|
||||||
self::setValue("station_podcast_download_counter", empty($c) ? 0 : --$c);
|
self::setValue("station_podcast_download_counter", empty($c) ? 0 : --$c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int either 0 (public) or 1 (private)
|
||||||
|
*/
|
||||||
public static function getStationPodcastPrivacy() {
|
public static function getStationPodcastPrivacy() {
|
||||||
if (!Billing::isStationPodcastAllowed()) {
|
if (!Billing::isStationPodcastAllowed()) {
|
||||||
// return private setting
|
// return private setting
|
||||||
|
|
|
@ -482,10 +482,15 @@ class Application_Model_Scheduler
|
||||||
->orderByDbStarts()
|
->orderByDbStarts()
|
||||||
->find($this->con);
|
->find($this->con);
|
||||||
|
|
||||||
|
$now = new DateTime("now", new DateTimeZone("UTC"));
|
||||||
$itemStartDT = $instance->getDbStarts(null);
|
$itemStartDT = $instance->getDbStarts(null);
|
||||||
foreach ($schedule as $item) {
|
foreach ($schedule as $item) {
|
||||||
$itemEndDT = $this->findEndTime($itemStartDT, $item->getDbClipLength());
|
$itemEndDT = $this->findEndTime($itemStartDT, $item->getDbClipLength());
|
||||||
Logging::info($itemEndDT);
|
// If the track has already ended, don't change it.
|
||||||
|
if ($itemEndDT < $now) {
|
||||||
|
$itemStartDT = $itemEndDT;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$item->setDbStarts($itemStartDT)
|
$item->setDbStarts($itemStartDT)
|
||||||
->setDbEnds($itemEndDT)
|
->setDbEnds($itemEndDT)
|
||||||
->save($this->con);
|
->save($this->con);
|
||||||
|
@ -515,10 +520,15 @@ class Application_Model_Scheduler
|
||||||
->orderByDbStarts()
|
->orderByDbStarts()
|
||||||
->find($this->con);
|
->find($this->con);
|
||||||
|
|
||||||
|
$now = new DateTime("now", new DateTimeZone("UTC"));
|
||||||
$itemStartDT = $instance->getDbStarts(null);
|
$itemStartDT = $instance->getDbStarts(null);
|
||||||
foreach ($schedule as $item) {
|
foreach ($schedule as $item) {
|
||||||
|
|
||||||
$itemEndDT = $this->findEndTime($itemStartDT, $item->getDbClipLength());
|
$itemEndDT = $this->findEndTime($itemStartDT, $item->getDbClipLength());
|
||||||
|
// If the track has already ended, don't change it.
|
||||||
|
if ($itemEndDT < $now) {
|
||||||
|
$itemStartDT = $itemEndDT;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$item->setDbStarts($itemStartDT)
|
$item->setDbStarts($itemStartDT)
|
||||||
->setDbEnds($itemEndDT);
|
->setDbEnds($itemEndDT);
|
||||||
|
@ -1191,7 +1201,7 @@ class Application_Model_Scheduler
|
||||||
|
|
||||||
foreach ($removedItems as $removedItem) {
|
foreach ($removedItems as $removedItem) {
|
||||||
$instance = $removedItem->getCcShowInstances($this->con);
|
$instance = $removedItem->getCcShowInstances($this->con);
|
||||||
$effectedInstanceIds[] = $instance->getDbId();
|
$effectedInstanceIds[$instance->getDbId()] = $instance->getDbId();
|
||||||
|
|
||||||
//check if instance is linked and if so get the schedule items
|
//check if instance is linked and if so get the schedule items
|
||||||
//for all linked instances so we can delete them too
|
//for all linked instances so we can delete them too
|
||||||
|
|
|
@ -193,13 +193,13 @@ $(document).ready(function() {
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>64kbps Stream Quality
|
<td>Up to 64kbps Stream Quality
|
||||||
</td>
|
</td>
|
||||||
<td>64kbps and 128kbps Stream Quality
|
<td>Up to 128kbps Stream Quality
|
||||||
</td>
|
</td>
|
||||||
<td>64kbps and 196kbps Stream Quality
|
<td>Up to 196kbps Stream Quality
|
||||||
</td>
|
</td>
|
||||||
<td class="last-column">64kbps, 128kbps, and 196kbps Stream Quality
|
<td class="last-column">Up to 196kbps Stream Quality
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -222,7 +222,18 @@ $(document).ready(function() {
|
||||||
<td class="last-column">
|
<td class="last-column">
|
||||||
150GB Storage
|
150GB Storage
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>No Built-in Podcast
|
||||||
|
</td>
|
||||||
|
<td>2,000 Podcast Episode Downloads
|
||||||
|
</td>
|
||||||
|
<td>5,000 Podcast Episode Downloads
|
||||||
|
</td>
|
||||||
|
<td class="last-column">
|
||||||
|
10,000 Podcast Episode Downloads
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Ticket, Email, Forum Support
|
<td>Ticket, Email, Forum Support
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<div id="weekly-schedule-widget" class="prefpanel ui-widget simple-formblock clearfix padded-strong ">
|
||||||
|
|
||||||
|
<?php $baseUrl = Application_Common_OsPath::getBaseDir(); ?>
|
||||||
|
|
||||||
|
<h2 style="float:left"><?php echo _("Facebook Radio Player") ?></h2>
|
||||||
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
|
<p><button class="btn btn-new" id="facebook-login" style="margin-left: 300px;">Add to My Facebook Page</button></p>
|
||||||
|
|
||||||
|
<div id="weekly-schedule-widget-error">
|
||||||
|
<?php echo $this->facebook_error_msg; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 style="padding-left: 0px">Preview:</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<iframe width=800 height=800 src="<?php echo Application_Common_HTTPHelper::getStationUrl(); ?>/?facebook=1"></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 style="padding-left: 0px">Tips:</h3>
|
||||||
|
<p>
|
||||||
|
To make the tab more visible on your Facebook page, click "More", and "Manage Tabs":<br>
|
||||||
|
<img src="<?php echo($baseUrl) . "images/doc/facebook_widget1.png"?>"><br><br>
|
||||||
|
|
||||||
|
Then, drag the Radio Player item higher in the list, and click Save. It will now appear as one of the default tabs instead of being buried under "More":<br>
|
||||||
|
<img src="<?php echo($baseUrl) . "images/doc/facebook_widget2.png"?>">
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("#facebook-login").click(function() {
|
||||||
|
AIRTIME.facebook.promptForFacebookPage();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
window.fbAsyncInit = function() {
|
||||||
|
FB.init({
|
||||||
|
appId : FACEBOOK_APP_ID,
|
||||||
|
xfbml : true,
|
||||||
|
version : 'v2.4'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var AIRTIME = (function(AIRTIME) {
|
||||||
|
|
||||||
|
//Module initialization
|
||||||
|
if (AIRTIME.facebook === undefined) {
|
||||||
|
AIRTIME.facebook = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var mod = AIRTIME.facebook;
|
||||||
|
|
||||||
|
(function (d, s, id) {
|
||||||
|
var js, fjs = d.getElementsByTagName(s)[0];
|
||||||
|
if (d.getElementById(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
js = d.createElement(s);
|
||||||
|
js.id = id;
|
||||||
|
js.src = "//connect.facebook.net/en_US/sdk.js";
|
||||||
|
fjs.parentNode.insertBefore(js, fjs);
|
||||||
|
}(document, 'script', 'facebook-jssdk'));
|
||||||
|
|
||||||
|
mod.promptForFacebookPage = function() {
|
||||||
|
FB.login(function (response) {
|
||||||
|
if (response.authResponse) {
|
||||||
|
mod.getPagesOwnedByUser(response.authResponse.userID, response.authResponse.accessToken);
|
||||||
|
mod.addPageTab();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log('Authorization failed.');
|
||||||
|
}
|
||||||
|
}, {scope: 'manage_pages'});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.getPagesOwnedByUser = function(userId, accessToken) {
|
||||||
|
FB.api('/' + userId + '/accounts', function (response) {
|
||||||
|
console.log(response);
|
||||||
|
}, {access_token: accessToken});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.addPageTab = function() {
|
||||||
|
FB.ui(
|
||||||
|
{ method: 'pagetab' },
|
||||||
|
function (resp) {
|
||||||
|
console.log("response:");
|
||||||
|
console.log(resp);
|
||||||
|
var pageIdList = [];
|
||||||
|
var tabs = resp["tabs_added"];
|
||||||
|
|
||||||
|
if ((tabs != undefined) && (Object.keys(tabs).length > 0)) {
|
||||||
|
for (var pageId in tabs) {
|
||||||
|
pageIdList.push(pageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
//POST these back to Airtime, which will then proxy it over to our social app. (multiple requests from Airtime)
|
||||||
|
$.post('facebook-tab-success', { "pages" : JSON.stringify(pageIdList) }, function() {
|
||||||
|
alert("Successfully added to your Facebook page!");
|
||||||
|
}).done(function() {
|
||||||
|
|
||||||
|
}).fail(function() {
|
||||||
|
alert("Sorry, an error occurred and we were unable to add the widget to your Facebook page.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
app_id: FACEBOOK_APP_ID,
|
||||||
|
//redirect_uri: 'https://localhost'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AIRTIME;
|
||||||
|
|
||||||
|
}(AIRTIME || {}));
|
||||||
|
|
|
@ -542,6 +542,18 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod.handleAjaxError = function (r) {
|
||||||
|
// If the request was denied due to permissioning
|
||||||
|
if (r.status === 403) {
|
||||||
|
// Hide the processing div
|
||||||
|
var wrapper = $("#library_display_wrapper");
|
||||||
|
wrapper.find(".dt-process-rel").hide();
|
||||||
|
wrapper.find('.empty_placeholder_text').text($.i18n._("You don't have permission to view the library."));
|
||||||
|
wrapper.find('.empty_placeholder').show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
libraryInit = function() {
|
libraryInit = function() {
|
||||||
|
|
||||||
$libContent = $("#library_content");
|
$libContent = $("#library_content");
|
||||||
|
@ -706,7 +718,7 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
"url": sSource,
|
"url": sSource,
|
||||||
"data": aoData,
|
"data": aoData,
|
||||||
"success": fnCallback,
|
"success": fnCallback,
|
||||||
"error": handleAjaxError
|
"error": mod.handleAjaxError
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
var filterMessage = $libContent.find('.filter-message');
|
var filterMessage = $libContent.find('.filter-message');
|
||||||
if (data.iTotalRecords > data.iTotalDisplayRecords) {
|
if (data.iTotalRecords > data.iTotalDisplayRecords) {
|
||||||
|
@ -904,17 +916,6 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAjaxError(r) {
|
|
||||||
// If the request was denied due to permissioning
|
|
||||||
if (r.status === 403) {
|
|
||||||
// Hide the processing div
|
|
||||||
$("#library_display_wrapper").find(".dt-process-rel").hide();
|
|
||||||
$('.empty_placeholder_text').text($.i18n._("You don't have permission to view the library."));
|
|
||||||
|
|
||||||
$('.empty_placeholder').show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var selected = $("a[href$='"+location.hash+"']"), table;
|
var selected = $("a[href$='"+location.hash+"']"), table;
|
||||||
if (selected.parent().data("selection-id") == AIRTIME.library.MediaTypeIntegerEnum.PODCAST) {
|
if (selected.parent().data("selection-id") == AIRTIME.library.MediaTypeIntegerEnum.PODCAST) {
|
||||||
table = mod.DataTableTypeEnum.PODCAST;
|
table = mod.DataTableTypeEnum.PODCAST;
|
||||||
|
|
|
@ -399,15 +399,9 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
Table.prototype._handleAjaxError = function(r) {
|
Table.prototype._handleAjaxError = function(r) {
|
||||||
// If the request was denied due to permissioning
|
// If the request was denied due to permissioning
|
||||||
if (r.status === 403) {
|
if (r.status === 403) {
|
||||||
// Hide the processing div
|
$(".dt-process-rel").hide();
|
||||||
/*
|
$('.empty_placeholder_text').text($.i18n._("You don't have permission to view this resource."));
|
||||||
$("#library_display_wrapper").find(".dt-process-rel").hide();
|
$('.empty_placeholder').show();
|
||||||
$.getJSON( "ajax/library_placeholders.json", function( data ) {
|
|
||||||
$('.empty_placeholder_text').text($.i18n._(data.unauthorized));
|
|
||||||
}) ;
|
|
||||||
|
|
||||||
$('.empty_placeholder').show();
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue