MT#56693 add parent sound set support

* sound sets can now use other sound sets as parents, in this
  case if a sound is missing for a sound set, it's taken from
  the parent one if it exists there, chained parents are also
  supported with the available sound files "accumulated" across
  them.
* add "expose_to_customer" field to sound sets, it enables exposing
  system sound sets to customers so they can be used as a parent or
  assigned to the 'sound_set' preference by 'subscriberadmin'.
* add "use_parent" field to sound set files, it's true by default
  (for existing or missing sound files) and if set to 0, then parents
  are not used for this particular sound file.
* use_parent column is hidden on the UI if a sound set does not have a
  parent assigned
* improve sound set handles list on the UI
    - Upload is renamed to Add
    - empty record are shown in the filename column as localised "(empty)"
      and Edit/Delete action buttons
* API /api/soundsets new 'parent_id' field, default null
* API /api/soundfiles new 'use_parent' field, default 1
* API /api/soundsets new 'expose_to_customer' field, default 0
* API /api/soundsets customer_id field now contains the customer id
  for 'subscriberadmin' role and 'null' if it's a system sound set
* system sound sets with expose_to_customer == 1 and within the same
  reseller are now visible to 'subscriberadmin' on the UI and via the
  API in read-only mode, same with the sound files that belong to the
  sound set. If a system sound set expose_to_customer is set back to 0,
  this sound set is automatically removed from all contract sound sets
  where it was assigned as a parent as well as from all subscriber
  'sound_set' preferences.
* contract_sound_set|sound_set preference API updates now have tigher
  checks for contract_id and also if the system sound set is exposed
  to the customer and belongs to the same reseller

Change-Id: I4908fd15e9c224d4c30794ceb8dae1b444bbf56a
mr11.3
Kirill Solomko 3 years ago
parent 16a82cb09d
commit 44ec794db2

@ -72,6 +72,25 @@ sub sets_list :Chained('/') :PathPart('sound') :CaptureArgs(0) {
return;
}
sub parent_sets_list :Chained('/') :PathPart('sound_parent') :CaptureArgs(0) {
my ( $self, $c ) = @_;
if($c->stash->{contract_rs}) {
NGCP::Panel::Utils::Sounds::stash_soundset_list(
c => $c,
fetch_parents => 1,
contract => $c->stash->{contract_rs}->first,
);
} else {
NGCP::Panel::Utils::Sounds::stash_soundset_list(
c => $c,
fetch_parents => 1,
);
}
$c->stash(template => 'sound/list.tt');
return;
}
sub contract_sets_list :Chained('/') :PathPart('sound/contract') :CaptureArgs(1) {
my ( $self, $c, $contract_id ) = @_;
@ -123,6 +142,18 @@ sub ajax :Chained('sets_list') :PathPart('ajax') :Args(0) {
return;
}
sub parent_ajax :Chained('parent_sets_list') :PathPart('ajax') :Args(0) {
my ($self, $c) = @_;
my $resultset = $c->stash->{sets_rs};
my $parents_rs = $resultset->search({
'me.id' => { '!=' => $c->session->{edit_sound_set_id} },
});
NGCP::Panel::Utils::Datatables::process($c, $parents_rs, $c->stash->{soundset_dt_columns});
$c->detach( $c->view("JSON") );
return;
}
sub contract_ajax :Chained('contract_sets_list') :PathPart('ajax') :Args(0) {
my ($self, $c) = @_;
@ -160,12 +191,20 @@ sub base :Chained('sets_list') :PathPart('') :CaptureArgs(1) {
sub edit :Chained('base') :PathPart('edit') {
my ($self, $c) = @_;
$self->check_subadmin_set_edit_access($c, $c->stash->{set_result});
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = { $c->stash->{set_result}->get_inflated_columns };
$params->{reseller}{id} = delete $params->{reseller_id};
$params->{contract}{id} = delete $params->{contract_id};
$params->{parent}{id} = delete $params->{parent_id};
$params = merge($params, $c->session->{created_objects});
$c->session->{edit_sound_set_id} = $c->stash->{set_result}->id;
$self->check_subadmin_set_edit_access($c, $c->stash->{set_result});
if ($c->user->roles eq "admin") {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Sound::AdminSet", $c);
} elsif ($c->user->roles eq "reseller") {
@ -191,7 +230,10 @@ sub edit :Chained('base') :PathPart('edit') {
back_uri => $c->req->uri,
);
if ($posted && $form->validated) {
my $parent_loop_err;
try {
my $own_id = $c->stash->{set_result}->id;
my $parent_id = $form->values->{parent_id} = $form->values->{parent}{id} // undef;
if ($c->user->roles eq "admin") {
$form->values->{reseller_id} = $form->values->{reseller}{id};
$form->values->{contract_id} = $form->values->{contract}{id} // undef;
@ -211,7 +253,28 @@ sub edit :Chained('base') :PathPart('edit') {
}
delete $form->values->{reseller};
delete $form->values->{contract};
delete $form->values->{parent};
if (NGCP::Panel::Utils::Sounds::check_parent_chain_for_loop(
$c, $own_id, $parent_id)
) {
$parent_loop_err =
$c->loc("one of the parent sound sets refers to this one as a parent");
die $parent_loop_err;
}
if ($c->user->roles eq 'subscriberadmin' &&
$c->stash->{set_result}->contract_id != $c->user->account_id) {
die "an attempt to edit a sound set that does not belong to the subscriberadmin";
}
$c->model('DB')->txn_do(sub {
if ($c->stash->{set_result}->expose_to_customer == 1 &&
$form->values->{expose_to_customer} == 0) {
# remove this sound set from all parents/prefs on subscriberadmin
NGCP::Panel::Utils::Sounds::revoke_exposed_sound_set($c, $own_id);
}
# if contract default is set, clear old ones first
if ($c->stash->{set_result}->contract_id && $form->values->{contract_default} == 1) {
$c->stash->{sets_rs}->search({
@ -239,7 +302,9 @@ sub edit :Chained('base') :PathPart('edit') {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update sound set'),
desc => $c->loc('Failed to update sound set' .
($parent_loop_err ? " ($parent_loop_err)" : '')
),
);
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/sound'));
@ -255,6 +320,8 @@ sub edit :Chained('base') :PathPart('edit') {
sub delete_sound :Chained('base') :PathPart('delete') {
my ($self, $c) = @_;
$self->check_subadmin_set_edit_access($c, $c->stash->{set_result});
try {
my $schema = $c->model('DB');
@ -341,6 +408,7 @@ sub create :Chained('sets_list') :PathPart('create') :Args() {
);
if($posted && $form->validated) {
try {
$form->values->{parent_id} = $form->values->{parent}{id};
if($c->user->roles eq "admin") {
$form->values->{reseller_id} = $form->values->{reseller}{id};
$form->values->{contract_id} = $form->values->{contract}{id} // undef;
@ -364,6 +432,7 @@ sub create :Chained('sets_list') :PathPart('create') :Args() {
}
delete $form->values->{reseller};
delete $form->values->{contract};
delete $form->values->{parent};
my $schema = $c->model('DB');
$schema->txn_do(sub {
@ -474,18 +543,24 @@ sub handles_base :Chained('handles_list') :PathPart('') :CaptureArgs(1) {
sub handles_edit :Chained('handles_base') :PathPart('edit') {
my ($self, $c) = @_;
$self->check_subadmin_handle_edit_access($c, $c->stash->{file_result});
my $posted = ($c->request->method eq 'POST');
my $upload = $c->req->upload('soundfile');
my %params = (
%{ $c->request->params },
soundfile => $posted ? $upload : undef,
);
$c->request->params->{soundfile} = $posted ? $upload : undef;
my $file_result = $c->stash->{file_result};
my $params = {
loopplay => $file_result->loopplay // 0,
use_parent => $file_result->use_parent // 0,
};
$params = merge($params, $c->session->{created_objects});
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Sound::File", $c);
$form->process(
posted => $posted,
params => \%params,
item => $file_result,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
@ -541,6 +616,7 @@ sub handles_edit :Chained('handles_base') :PathPart('edit') {
try {
$file_result->update({
loopplay => $form->values->{loopplay},
use_parent => $form->values->{use_parent},
filename => $filename,
data => $soundfile,
codec => $target_codec,
@ -560,6 +636,7 @@ sub handles_edit :Chained('handles_base') :PathPart('edit') {
try {
$file_result->update({
loopplay => $form->values->{loopplay},
use_parent => $form->values->{use_parent},
});
NGCP::Panel::Utils::Message::info(
c => $c,
@ -584,6 +661,8 @@ sub handles_edit :Chained('handles_base') :PathPart('edit') {
sub handles_delete :Chained('handles_base') :PathPart('delete') {
my ($self, $c) = @_;
$self->check_subadmin_handle_edit_access($c, $c->stash->{file_result});
try {
$c->stash->{file_result}->delete;
NGCP::Panel::Utils::Message::info(
@ -615,6 +694,8 @@ sub handles_delete :Chained('handles_base') :PathPart('delete') {
sub handles_download :Chained('handles_base') :PathPart('download') :Args(0) {
my ($self, $c) = @_;
$self->check_subadmin_handle_download_access($c, $c->stash->{file_result});
my $file = $c->stash->{file_result};
my $filename = $file->filename;
$filename =~ s/\.\w+$/.wav/;
@ -695,6 +776,60 @@ sub handles_load_default :Chained('handles_list') :PathPart('loaddefault') :Args
return;
}
sub check_subadmin_set_edit_access {
my ($self, $c, $set) = @_;
if ($c->user->roles eq 'subscriberadmin') {
if ($set->contract_id && $set->contract_id == $c->user->account_id) {
return 1;
}
$c->detach('/denied_page');
}
return 1;
}
sub check_subadmin_handle_edit_access {
my ($self, $c, $file_result) = @_;
if ($c->user->roles eq 'subscriberadmin') {
my $set = $file_result->set;
if ($set->contract_id && $set->contract_id == $c->user->account_id) {
return 1;
}
$c->detach('/denied_page');
}
return 1;
}
sub check_subadmin_handle_download_access {
my ($self, $c, $file_result) = @_;
if ($c->user->roles eq 'subscriberadmin') {
my $set = $file_result->set;
my $contract_rs = $c->stash->{contract_rs} //
NGCP::Panel::Utils::Contract::get_contract_rs(schema => $c->model('DB'))->search_rs({
'me.id' => $c->user->account_id,
});
my $contract = $contract_rs->first;
if (!$set->contract_id &&
$set->expose_to_customer == 1 &&
$set->reseller_id == $contract->contact->reseller_id) {
return 1;
} elsif ($set->contract_id &&
$set->contract_id == $c->user->account_id) {
return 1;
}
$c->detach('/denied_page');
}
return 1;
}
1;
# vim: set tabstop=4 expandtab:

@ -2652,7 +2652,8 @@ sub load_preference_list :Private {
my $sound_sets_rs = $c->model('DB')
->resultset('voip_sound_sets')->search({
reseller_id => $reseller_id,
contract_id => undef });
contract_id => undef,
expose_to_customer => 1 });
$c->stash(sound_sets_rs => $sound_sets_rs,
sound_sets => [$sound_sets_rs->all]);

@ -0,0 +1,20 @@
package NGCP::Panel::Field::ParentSoundSet;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Field::Compound';
has_field 'id' => (
type => '+NGCP::Panel::Field::DataTable',
label => 'Parent',
do_label => 0,
do_wrapper => 0,
required => 1,
template => 'helpers/datatables_field.tt',
ajax_src => '/sound_parent/ajax',
table_titles => ['#', 'Name', 'Description', 'Parent'],
table_fields => ['id', 'name', 'description', 'parent.name'],
);
no Moose;
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,20 @@
package NGCP::Panel::Field::ParentSoundSetAdmin;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Field::Compound';
has_field 'id' => (
type => '+NGCP::Panel::Field::DataTable',
label => 'Parent',
do_label => 0,
do_wrapper => 0,
required => 1,
template => 'helpers/datatables_field.tt',
ajax_src => '/sound_parent/ajax',
table_titles => ['#', 'Reseller', 'Name', 'Description', 'Parent'],
table_fields => ['id', 'reseller_name', 'name', 'description', 'parent.name'],
);
no Moose;
1;
# vim: set tabstop=4 expandtab:

@ -12,10 +12,20 @@ has_field 'reseller' => (
},
);
has_field 'parent' => (
type => '+NGCP::Panel::Field::ParentSoundSetAdmin',
label => 'Parent',
validate_when_empty => 0,
element_attr => {
rel => ['tooltip'],
title => ['Parent sound set. If used, missing sound of the current sound set will used from the parent one (except for those with use_parent = 0)'],
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/reseller contract name description contract_default/],
render_list => [qw/reseller contract name description expose_to_customer contract_default parent/],
);
1;

@ -16,7 +16,7 @@ has_field 'reseller_id' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/reseller_id customer_id name description contract_default copy_from_default language loopplay replace_existing/],
render_list => [qw/reseller_id customer_id name description expose_to_customer contract_default parent_id copy_from_default language loopplay replace_existing/],
);

@ -30,6 +30,16 @@ has_field 'contract_default' => (
},
);
has_field 'parent' => (
type => '+NGCP::Panel::Field::ParentSoundSet',
label => 'Parent',
validate_when_empty => 0,
element_attr => {
rel => ['tooltip'],
title => ['Parent sound set. If used, missing sound of the current sound set will used from the parent one (except for those with use_parent = 0)'],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
@ -40,7 +50,7 @@ has_field 'save' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/name description contract_default/],
render_list => [qw/name description contract_default parent/],
);
has_block 'actions' => (

@ -15,6 +15,10 @@ has_field 'loopplay' => (
type => 'Boolean',
);
has_field 'use_parent' => (
type => 'Boolean',
);
has_field 'soundfile' => (
type => 'Upload',
noupdate => 1, #let controller handle the file
@ -31,7 +35,7 @@ has_field 'save' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/loopplay soundfile/],
render_list => [qw/loopplay use_parent soundfile/],
);
has_block 'actions' => (

@ -18,6 +18,16 @@ has_field 'loopplay' => (
},
);
has_field 'use_parent' => (
type => 'Boolean',
required => 0,
default => 1,
element_attr => {
rel => ['tooltip'],
title => ['Use the parent sound set if this sound does not have a file.'],
},
);
has_field 'filename' => (
type => 'Text',
required => 1,

@ -53,4 +53,4 @@ has_field 'replace_existing' => (
required => 0,
);
1;
1;

@ -38,6 +38,14 @@ has_field 'description' => (
},
);
has_field 'expose_to_customer' => (
type => 'Boolean',
element_attr => {
rel => ['tooltip'],
title => ['Allow customers to use this sound set'],
},
);
has_field 'contract_default' => (
type => 'Boolean',
label => 'Default for Subscribers',
@ -47,6 +55,16 @@ has_field 'contract_default' => (
},
);
has_field 'parent' => (
type => '+NGCP::Panel::Field::ParentSoundSet',
label => 'Parent',
validate_when_empty => 0,
element_attr => {
rel => ['tooltip'],
title => ['Parent sound set. If used, missing sound of the current sound set will used from the parent one (except for those with use_parent = 0)'],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
@ -57,7 +75,7 @@ has_field 'save' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/contract name description contract_default/],
render_list => [qw/contract name description expose_to_customer contract_default parent/],
);
has_block 'actions' => (

@ -13,10 +13,18 @@ has_field 'contract_id' => (
},
);
has_field 'expose_to_customer' => (
type => 'Boolean',
element_attr => {
rel => ['tooltip'],
title => ['Allow customers to use this sound set'],
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/customer_id name description contract_default copy_from_default language loopplay replace_existing/],
render_list => [qw/customer_id name description expose_to_customer contract_default parent_id copy_from_default language loopplay replace_existing/],
);

@ -21,6 +21,17 @@ has_field 'description' => (
},
);
has_field 'contract_id' => (
type => 'PosInteger',
label => 'Customer',
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['The contract used for this subscriber.']
},
);
has_field 'contract_default' => (
type => 'Boolean',
label => 'Default for Subscribers',
@ -30,6 +41,16 @@ has_field 'contract_default' => (
},
);
has_field 'parent_id' => (
type => 'PosInteger',
label => 'Parent',
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['Parent sound set that is used to substitute missing sound file of the current one.']
},
);
has_field 'copy_from_default' => (
type => 'Boolean',
label => 'Use system default sound files',
@ -39,7 +60,7 @@ has_field 'copy_from_default' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/name description contract_default copy_from_default language loopplay replace_existing/],
render_list => [qw/name description contract_id contract_default parent_id copy_from_default language loopplay replace_existing/],
);
# TODO: inheritance?

@ -50,8 +50,15 @@ sub _item_rs {
join => 'set',
});
} elsif ($c->user->roles eq "subscriberadmin") {
my $contract = $c->model('DB')->resultset('contracts')->find($c->user->account_id);
$item_rs = $item_rs->search({
'set.contract_id' => $c->user->account_id,
-or => [
'set.contract_id' => $c->user->account_id,
-and => [ 'set.contract_id' => undef,
'set.reseller_id' => $contract->contact->reseller_id,
'set.expose_to_customer' => 1,
],
]
},{
join => 'set',
});
@ -152,6 +159,14 @@ sub update_item {
return;
}
if ($c->user->roles eq 'subscriberadmin') {
if (!$set->contract_id || $set->contract_id != $c->user->account_id) {
$c->log->error("Cannot modify read-only sound file that does not belong to this subscriberadmin");
$self->error($c, HTTP_FORBIDDEN, "Cannot modify read-only sound file");
return;
}
}
my $handle_rs = $c->model('DB')->resultset('voip_sound_handles')->search({
'me.name' => $resource->{handle},
});

@ -9,6 +9,7 @@ use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::Sounds;
sub resource_name{
return 'soundsets';
@ -24,9 +25,16 @@ sub _item_rs {
'reseller_id' => $c->user->reseller_id,
});
} elsif ($c->user->roles eq "subscriberadmin") {
my $contract = $c->model('DB')->resultset('contracts')->find($c->user->account_id);
$item_rs = $item_rs->search_rs({
-or => [
'contract_id' => $c->user->account_id,
});
-and => [ 'contract_id' => undef,
'reseller_id' => $contract->contact->reseller_id,
'expose_to_customer' => 1,
],
]
});
} else {
return; # subscriber role not allowed
}
@ -59,6 +67,12 @@ sub post_process_hal_resource {
#Currently actual field is contract_id, as it is specified in the form and thus in the doc
#But we will keep customer_id for backward compatibility
$resource->{customer_id} = $resource->{contract_id};
# delete contract_id as it has not been exposed to 'subscriberadmin' and therefore,
# does not need to duplicate the customer_id field
if ($c->user->roles eq 'subscriberadmin') {
delete $resource->{contract_id};
}
return $resource;
}
@ -117,8 +131,52 @@ sub check_resource{
return;
}
}
if ($c->user->roles eq 'subscriberadmin' && $c->request->method ne 'POST' &&
(!$old_resource->{contract_id} || $old_resource->{contract_id} != $c->user->account_id)) {
$c->log->error("Cannot modify read-only sound set that does not belong to this subscriberadmin");
$self->error($c, HTTP_FORBIDDEN, "Cannot modify read-only sound set");
return;
}
if ($resource->{parent_id}) {
my $parent = $c->model('DB')->resultset('voip_sound_sets')->find($resource->{parent_id});
if (!$parent) {
$c->log->error("Invalid parent_id '$$resource{parent_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Parent sound set does not exist");
return;
}
if ($c->user->roles ne 'admin' && $reseller->id != $parent->reseller_id) {
$c->log->error("parent_id '$$resource{parent_id}' doesn't belong to reseller_id '$$resource{reseller_id}");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid reseller for parent sound set");
return;
}
if ($c->req->method eq 'PUT' || $c->req->method eq 'PATCH') {
my $loop = NGCP::Panel::Utils::Sounds::check_parent_chain_for_loop(
$c, $old_resource->{id}, $resource->{parent_id}
);
if ($loop) {
$c->log->error("parent_id '$$resource{parent_id}' one of the parent sound sets refers to this one as a parent");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Cannot use the parent sound set");
return;
}
}
}
return 1;
}
sub update_item_model {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
$item->update($resource);
if ($old_resource->{expose_to_customer} && !$resource->{expose_to_customer}) {
NGCP::Panel::Utils::Sounds::revoke_exposed_sound_set($c, $item->id);
}
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -893,11 +893,32 @@ sub update_preferences {
last SWITCH;
};
/^(contract_)?sound_set$/ && do {
# TODO: not applicable for domains, but for subs, check for contract_id!
my $set = $c->model('DB')->resultset('voip_sound_sets')->find({
my $is_contract_sound_set = $1 ? 1 : 0;
my $set_rs = $c->model('DB')->resultset('voip_sound_sets')->search({
name => $resource->{$pref},
$pref_type ne 'peer_pref' ? (reseller_id => $reseller_id) : (),
});
if ($pref_type ne 'peer_pref') {
$set_rs = $set_rs->search({
reseller_id => $reseller_id,
});
}
if ($pref_type eq 'usr_pref' && $c->user->roles eq 'subscriberadmin') {
my $contract_id = $elem->contract->id;
if ($is_contract_sound_set) {
$set_rs = $set_rs->search({
reseller_id => $reseller_id,
contract_id => $contract_id,
});
} else {
$set_rs = $set_rs->search({
'contract_id' => undef,
'reseller_id' => $reseller_id,
'expose_to_customer' => 1,
});
}
}
my $set = $set_rs->first;
unless($set) {
$c->log->error("no $pref '".$resource->{$pref}."' for reseller id $reseller_id found");
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown $pref '".$resource->{$pref}."'");

@ -85,29 +85,73 @@ sub stash_soundset_list {
my $c = $params{c};
my $contract = $params{contract};
my $fetch_parents = $params{fetch_parents} // 0;
my $sets_rs = $c->model('DB')->resultset('voip_sound_sets');
if($contract) {
$sets_rs = $sets_rs->search({ 'me.contract_id' => $contract->id });
}
my $sets_rs = $c->model('DB')->resultset('voip_sound_sets')->search({
},{
join => 'parent',
});
my $dt_fields = [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'name', search => 1, title => $c->loc('Name') },
{ name => 'description', search => 1, title => $c->loc('Description') },
{ name => 'parent.name', search => 1, title => $c->loc('Parent') },
];
if($c->user->roles eq "admin") {
if ($c->user->roles eq "admin") {
splice @{ $dt_fields }, 1, 0,
{ name => 'reseller.name', search => 1, title => $c->loc('Reseller') };
splice @{ $dt_fields }, 2, 0,
{ name => 'contract.contact.email', search => 1, title => $c->loc('Customer') };
} elsif($c->user->roles eq "reseller") {
push @{ $dt_fields },
{ name => 'expose_to_customer', search => 1, title => $c->loc('Expose to Customer') },
} elsif ($c->user->roles eq "reseller") {
splice @{ $dt_fields }, 1, 0,
{ name => 'contract.contact.email', search => 1, title => $c->loc('Customer') };
push @{ $dt_fields },
{ name => 'expose_to_customer', search => 1, title => $c->loc('Expose to Customer') },
$sets_rs = $sets_rs->search({ 'me.reseller_id' => $c->user->reseller_id });
} elsif($c->user->roles eq "subscriberadmin" && !$contract) {
$sets_rs = $sets_rs->search({ 'me.contract_id' => $c->user->account_id });
}
if ($contract || $c->user->roles eq "subscriberadmin") {
my $contract = $contract;
unless ($contract) {
my $contract_rs = $c->stash->{contract_rs} //
NGCP::Panel::Utils::Contract::get_contract_rs(schema => $c->model('DB'))->search_rs({
'me.id' => $c->user->account_id,
});
$contract = $contract_rs->first;
}
my $user_role = $c->user->roles;
my $user_contract_id = $contract->id;
$sets_rs = $sets_rs->search({
-or => [
'me.contract_id' => $contract->id,
-and => [ 'me.contract_id' => undef,
'me.reseller_id' => $contract->contact->reseller_id,
'me.expose_to_customer' => 1,
],
],
});
if (!$fetch_parents) {
$sets_rs = $sets_rs->search({
},{
'+select' => [ { '' => \[ "select '$user_role'" ], -as => 'user_role' },
{ '' => \[ "select '$user_contract_id'" ], -as => 'user_contract_id' },
'me.contract_id' ,
]
});
}
push @{ $dt_fields },
{ name => 'user_role', visible => 0, search => 0, title => $c->loc('#UserRole') },
{ name => 'user_contract_id', visible => 0, search => 0, title => $c->loc('#UserContractId') },
{ name => 'contract_id', visible => 0, search => 0, title => $c->loc('#Contract_id') },
}
$c->stash->{soundset_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, $dt_fields);
@ -126,8 +170,9 @@ sub get_handles_rs {
my $handles_rs = $c->model('DB')->resultset('voip_sound_groups')
->search({
},{
select => ['groups.name', \'handles.name', \'handles.id', 'files.filename', 'files.loopplay', 'files.codec', 'files.id'],
as => [ 'groupname', 'handlename', 'handleid', 'filename', 'loopplay', 'codec', 'fileid'],
select => ['groups.name', \'handles.name', \'handles.id', 'files.filename', 'files.loopplay', 'files.codec', 'files.id',
\'IF(files.use_parent IS NOT NULL, files.use_parent, 1)'],
as => [ 'groupname', 'handlename', 'handleid', 'filename', 'loopplay', 'codec', 'fileid', 'use_parent'],
alias => 'groups',
from => [
{ groups => 'provisioning.voip_sound_groups' },
@ -265,6 +310,49 @@ sub subcriber_sound_set_update_or_create {
}
}
sub check_parent_chain_for_loop {
my ($c, $set_id, $parent_id) = @_;
return 0 if !$set_id;
return 0 if !$parent_id;
return 1 if $set_id == $parent_id;
my $rs = $c->model('DB')->resultset('v_sound_set_files')->search({
'me.set_id' => $parent_id,
});
if ($rs->first) {
if (my $parent_chain = $rs->first->parent_chain) {
my @parents = split /:/, $parent_chain;
foreach my $chain_parent_id (@parents) {
if ($set_id == $chain_parent_id) {
return 1;
}
}
}
}
return 0;
}
sub revoke_exposed_sound_set {
my ($c, $set_id) = @_;
my $used_customer_sets_rs = $c->model('DB')->resultset('voip_sound_sets')->search({
parent_id => $set_id,
contract_id => { '!=' => undef },
});
$used_customer_sets_rs->update({ parent_id => undef });
my $used_subscriber_prefs_rs = $c->model('DB')->resultset('voip_usr_preferences')->search({
'attribute.attribute' => 'sound_set',
value => $set_id,
},{
join => 'attribute',
});
$used_subscriber_prefs_rs->delete;
}
1;
# vim: set tabstop=4 expandtab:

@ -444,8 +444,10 @@ $(function() {
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Edit'), uri = "/sound/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
{ name = c.loc('Delete'), uri = "/sound/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' },
{ name = c.loc("Edit"), uri = "/sound/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit',
condition => 'full.user_role != "subscriberadmin" || (full.user_contract_id == full.contract_id)' },
{ name = c.loc('Delete'), uri = "/sound/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash',
condition => 'full.user_role != "subscriberadmin" || (full.user_contract_id == full.contract_id)' },
{ name = c.loc('Files'), uri = "/sound/'+full.id+'/handles", class = 'btn-small btn-tertiary', icon = 'icon-list' },
];
helper.top_buttons = [

@ -1,4 +1,6 @@
[% site_config.title = c.loc('Manage Sound Set [_1]', set_result.name) -%]
[% site_config.title = c.loc('Manage Sound Set [_1] [_2]', set_result.name, set_result.parent_id ? '(Parent: ' _ set_result.parent.name _ ')' : '') -%]
[% can_edit_handles = ((c.user.roles == 'subscriberadmin' && c.user.account_id == set_result.contract_id) || (c.user.roles != 'subscriberadmin' && !c.user.read_only)) ? 1 : 0 -%]
<div class="row">
<span>
@ -39,6 +41,9 @@
<th>[% c.loc('Name') %]</th>
<th>[% c.loc('Filename') %]</th>
<th>[% c.loc('Loop') %]</th>
[% IF set_result.parent_id -%]
<th>[% c.loc('Use Parent') %]</th>
[% END -%]
[% # one for actions -%]
<th></th>
</tr>
@ -50,21 +55,35 @@
[% r.get_column("handlename") %]
</td>
<td>
[% r.get_column("filename") %]
[% r.get_column("fileid")
? r.get_column("filename")
? r.get_column("filename") : c.loc("(empty)")
: ''
%]
</td>
<td>
<input type="checkbox" disabled="disabled" [% r.get_column('loopplay') ? 'checked="checked"' : '' %]>
</td>
[% IF set_result.parent_id -%]
<td>
<input type="checkbox" disabled="disabled" [% r.get_column('use_parent') == 0 ? '' : 'checked="checked"' %]>
</td>
[% END -%]
<td>
<div class="sw_actions pull-right">
[% IF r.get_column("filename").size -%]
[% UNLESS c.user.read_only -%]
[% IF can_edit_handles -%]
<a class="btn btn-small btn-primary" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/edit" %]"><i class="icon-edit"></i> [% c.loc('Edit') %]</a>
<a class="btn btn-small btn-secondary" data-confirm="Delete" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/delete" %]"><i class="icon-edit"></i> [% c.loc('Delete') %]</a>
[% END -%]
<a class="btn btn-small btn-tertiary" href="[% c.uri_for_action('/sound/handles_download', [c.req.captures.0, r.get_column('handleid')]) %]"><i class="icon-play"></i> [% c.loc('Play') %]</a>
[% ELSIF !c.user.read_only -%]
<a class="btn btn-small btn-primary" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/edit" %]"><i class="icon-edit"></i> [% c.loc('Upload') %]</a>
[% ELSIF r.get_column("fileid") -%]
[% IF can_edit_handles -%]
<a class="btn btn-small btn-primary" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/edit" %]"><i class="icon-edit"></i> [% c.loc('Edit') %]</a>
<a class="btn btn-small btn-secondary" data-confirm="Delete" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/delete" %]"><i class="icon-edit"></i> [% c.loc('Delete') %]</a>
[% END -%]
[% ELSIF can_edit_handles -%]
<a class="btn btn-small btn-primary" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/edit" %]"><i class="icon-edit"></i> [% c.loc('Add') %]</a>
[% END -%]
</div>
</td>

Loading…
Cancel
Save