Split display status & type property into two separate controllers

This commit is contained in:
Nicolas Le Goff
2012-10-15 17:53:21 +02:00
parent f047c83c17
commit bda5821635
4 changed files with 248 additions and 128 deletions

View File

@@ -38,11 +38,11 @@ class Property implements ControllerProviderInterface
}); });
/** /**
* Display records property * Display records status property
* *
* name : display_property * name : display_status_property
* *
* description : Display records property * description : Display records status property
* *
* method : GET * method : GET
* *
@@ -50,8 +50,24 @@ class Property implements ControllerProviderInterface
* *
* return : HTML Response * return : HTML Response
*/ */
$controllers->get('/', $this->call('displayProperty')) $controllers->get('/', $this->call('displayStatusProperty'))
->bind('display_property'); ->bind('display_status_property');
/**
* Display records status property
*
* name : display_type_property
*
* description : Display records status property
*
* method : GET
*
* parameters : none
*
* return : HTML Response
*/
$controllers->get('/type/', $this->call('displayTypeProperty'))
->bind('display_type_property');
/** /**
* Change records status * Change records status
@@ -89,83 +105,55 @@ class Property implements ControllerProviderInterface
} }
/** /**
* Display property * Display Status property
* *
* @param Application $app * @param Application $app
* @param Request $request * @param Request $request
* @return Response * @return Response
*/ */
public function displayProperty(Application $app, Request $request) public function displayStatusProperty(Application $app, Request $request)
{ {
if (!$request->isXmlHttpRequest()) { if (!$request->isXmlHttpRequest()) {
$app->abort(400); $app->abort(400);
} }
$records = RecordsRequest::fromRequest($app, $request, false, array('chgstatus', 'canmodifrecord')); $records = RecordsRequest::fromRequest($app, $request, false, array('chgstatus'));
$databoxStatus = \databox_status::getDisplayStatus($app); $databoxStatus = \databox_status::getDisplayStatus($app);
$statusBit = $recordsType = $nRec = $toRemove = array(); $statusBit = $nRec = array();
foreach ($records as $key => $record) { foreach ($records as $record) {
if (!$app['phraseanet.user']->ACL()->has_hd_grant($record) ||
!$app['phraseanet.user']->ACL()->has_preview_grant($record)) {
try {
$stmt = $record->get_databox()->get_connection()->prepare(sprintf('SELECT record_id FROM record WHERE ((status ^ %s) & %s) = 0 AND record_id = :record_id', $app['phraseanet.user']->ACL()->get_mask_xor($record->get_base_id()), $app['phraseanet.user']->ACL()->get_mask_and($record->get_base_id())));
$stmt->execute(array(':record_id' => $record->get_record_id()));
if (0 === $stmt->rowCount()) { if ($this->isEligible($app, $record)) {
$toRemove[] = $key; //perform logic
} else { $sbasId = $record->get_databox()->get_sbas_id();
//perform logic
$sbasId = $record->get_databox()->get_sbas_id();
if (!isset($nRec[$sbasId])) { if (!isset($nRec[$sbasId])) {
$nRec[$sbasId] = array('stories' => 0, 'records' => 0); $nRec[$sbasId] = array('stories' => 0, 'records' => 0);
} }
$nRec[$sbasId]['records']++; $nRec[$sbasId]['records']++;
if ($record->is_grouping()) { if ($record->is_grouping()) {
$nRec[$sbasId]['stories']++; $nRec[$sbasId]['stories']++;
} }
if (!isset($recordsType[$sbasId])) { if (!isset($statusBit[$sbasId])) {
$recordsType[$sbasId] = array();
}
if (!isset($recordsType[$sbasId][$record->get_type()])) { $statusBit[$sbasId] = isset($databoxStatus[$sbasId]) ? $databoxStatus[$sbasId] : array();
$recordsType[$sbasId][$record->get_type()] = array();
}
$recordsType[$sbasId][$record->get_type()][] = $record; foreach (array_keys($statusBit[$sbasId]) as $bit) {
$statusBit[$sbasId][$bit]['nset'] = 0;
if (!isset($statusBit[$sbasId])) {
$statusBit[$sbasId] = isset($databoxStatus[$sbasId]) ? $databoxStatus[$sbasId] : array();
foreach (array_keys($statusBit[$sbasId]) as $bit) {
$statusBit[$sbasId][$bit]['nset'] = 0;
}
}
$status = strrev($record->get_status());
foreach (array_keys($statusBit[$sbasId]) as $bit) {
$statusBit[$sbasId][$bit]["nset"] += substr($status, $bit, 1) !== "0" ? 1 : 0;
}
} }
}
$stmt->closeCursor(); $status = strrev($record->get_status());
unset($stmt);
} catch (\Exception $e) { foreach (array_keys($statusBit[$sbasId]) as $bit) {
$toRemove[] = $key; $statusBit[$sbasId][$bit]["nset"] += substr($status, $bit, 1) !== "0" ? 1 : 0;
} }
} }
} }
foreach ($toRemove as $key) {
$records->remove($key);
}
foreach ($records->databoxes() as $databox) { foreach ($records->databoxes() as $databox) {
$sbasId = $databox->get_sbas_id(); $sbasId = $databox->get_sbas_id();
foreach ($statusBit[$sbasId] as $bit => $values) { foreach ($statusBit[$sbasId] as $bit => $values) {
@@ -174,10 +162,50 @@ class Property implements ControllerProviderInterface
} }
return new Response($app['twig']->render('prod/actions/Property/index.html.twig', array( return new Response($app['twig']->render('prod/actions/Property/index.html.twig', array(
'records' => $records,
'statusBit' => $statusBit,
'nRec' => $nRec
)));
}
/**
* Display type property
*
* @param Application $app
* @param Request $request
* @return Response
*/
public function displayTypeProperty(Application $app, Request $request)
{
if (!$request->isXmlHttpRequest()) {
$app->abort(400);
}
$records = RecordsRequest::fromRequest($app, $request, false, array('canmodifrecord'));
$recordsType = array();
foreach ($records as $record) {
if ($this->isEligible($app, $record)) {
//perform logic
$sbasId = $record->get_databox()->get_sbas_id();
if (!isset($recordsType[$sbasId])) {
$recordsType[$sbasId] = array();
}
if (!isset($recordsType[$sbasId][$record->get_type()])) {
$recordsType[$sbasId][$record->get_type()] = array();
}
$recordsType[$sbasId][$record->get_type()][] = $record;
}
}
return new Response($app['twig']->render('prod/actions/Property/type.html.twig', array(
'records' => $records, 'records' => $records,
'statusBit' => $statusBit,
'recordsType' => $recordsType, 'recordsType' => $recordsType,
'nRec' => $nRec
))); )));
} }
@@ -277,6 +305,38 @@ class Property implements ControllerProviderInterface
return null; return null;
} }
/**
*
* @param Application $app
* @param record_adapter $record
* @return boolean
*/
private function isEligible(Application $app, \record_adapter $record)
{
$eligible = false;
if (!$app['phraseanet.user']->ACL()->has_hd_grant($record) ||
!$app['phraseanet.user']->ACL()->has_preview_grant($record)) {
try {
$stmt = $record->get_databox()->get_connection()->prepare(sprintf('SELECT record_id FROM record WHERE ((status ^ %s) & %s) = 0 AND record_id = :record_id', $app['phraseanet.user']->ACL()->get_mask_xor($record->get_base_id()), $app['phraseanet.user']->ACL()->get_mask_and($record->get_base_id())));
$stmt->execute(array(':record_id' => $record->get_record_id()));
if ($stmt->rowCount() > 0) {
$eligible = true;
}
$stmt->closeCursor();
unset($stmt);
} catch (\Exception $e) {
}
} else {
$eligible = true;
}
return $eligible;
}
/** /**
* Prefix the method to call with the controller class name * Prefix the method to call with the controller class name
* *

View File

@@ -1,12 +1,16 @@
{% import 'common/thumbnail.html.twig' as thumbnail %}
{% set nbReceivedDocuments = records.received().count() %} {% set nbReceivedDocuments = records.received().count() %}
{% set nbEditableDocuments = records.count() %} {% set nbEditableDocuments = records.count() %}
<div id='tabs-records-property'> <div id='tabs-records-property'>
{# This value is fetched when click on 2nd tab #}
<input type="hidden" name='original_selection' value="{{ app.request.query.get('lst') }}">
<ul> <ul>
<li><a href="#property-statut">{% trans 'Records Statut' %}</a></li> <li><a href="#property-statut">{% trans 'Records Statut' %}</a></li>
<li><a href="#property-type">{% trans 'Records type' %}</a></li> {# <span>&nbsp;</span> element is required for the jQuery loading spinner appears && disappears properly #}
<li><a href="/prod/records/property/type/">{% trans 'Records type' %}&nbsp;<span>&nbsp;</span></a></li>
</ul> </ul>
<div id='property-statut'> <div id='property-statut'>
@@ -112,75 +116,28 @@
{% endfor %} {% endfor %}
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn btn-primary submiter">{% trans "Apply changes" %}</button> <button type="button" class="btn btn-primary submiter">{% trans "Apply changes" %}</button>
<button type="button" class="btn">{% trans "Cancel" %}</button> <button type="button" class="btn cancel">{% trans "Cancel" %}</button>
</div> </div>
</form> </form>
</div> </div>
<div id='property-type'>
{% set typesEnum = [
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_AUDIO'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_VIDEO'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_DOCUMENT'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_FLASH'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_IMAGE')
]
%}
<form name="change-records-type" action="/prod/records/property/type/" method="POST">
<div class='well-small 'style="text-align:center;">
<select name='force_types' style="width:250px">
<option value="">{% trans 'Apply to all selected documents' %}</option>
{% for option in typesEnum %}
<option value="{{ option }}">{{ option }}</option>
{% endfor %}
</select>
</div>
<input name="lst" type="hidden" value="{{ records.serializedList() }}"/>
{% for sbasId,databoxTypes in recordsType %}
<h2 style="text-align:center;" clas="header">{{ sbasId|sbas_names(app) }}</h2>
{% for currentType, recordsByType in databoxTypes %}
<ul class="thumbnails" style="margin:20px auto;">
{% for record in recordsByType %}
<li class="span2">
<div class="thumbnail" style='min-height:205px'>
{{ thumbnail.format(record.get_thumbnail(), 160, 120, '', false, false) }}
<div class="caption">
<h5>{{ record.get_title() }}</h5>
<p>
<select name="types[{{record.get_serialize_key()}}]" style='width:100%'>
{% for option in typesEnum %}
<option value="{{ option }}" {{ record.is_grouping() ? "disabled='disabled'": "" }} {{ option == record.get_type() ? "selected='selected'" : '' }}>{{ option }}</option>
{% endfor %}
</select>
</p>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
<div class="form-actions">
<button type="button" class="btn btn-primary submiter">{% trans "Apply changes" %}</button>
<button type="button" class="btn">{% trans "Cancel" %}</button>
</div>
</form>
</div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
$("#tabs-records-property").tabs(); $("#tabs-records-property").tabs({
ajaxOptions: {
data : {
lst: $("input[name=original_selection]", $(this)).val()
}
},
cache: true //Load template only once
});
var $dialog = p4.Dialog.get(1); var $dialog = p4.Dialog.get(1);
var $dialogBox = $dialog.getDomElement(); var $dialogBox = $dialog.getDomElement();
var button = $(".submiter", $dialogBox); $("button.submiter", $dialogBox).bind("click", function(){
var $this = $(this);
button.bind("click", function(){
var form = $(this).closest("form"); var form = $(this).closest("form");
$.ajax({ $.ajax({
@@ -189,14 +146,14 @@
data: form.serializeArray(), data: form.serializeArray(),
dataType: 'json', dataType: 'json',
beforeSend:function(){ beforeSend:function(){
button.attr("disabled", true); $this.attr("disabled", true);
//@todo add loader //@todo add loader
}, },
success: function(data){ success: function(data){
$dialog.Close(1); $dialog.Close();
}, },
complete: function(){ complete: function(){
button.attr("disabled", false); $this.attr("disabled", false);
} }
}); });
}); });

View File

@@ -0,0 +1,78 @@
{% import 'common/thumbnail.html.twig' as thumbnail %}
{% set typesEnum = [
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_AUDIO'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_VIDEO'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_DOCUMENT'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_FLASH'),
constant('\\Alchemy\\Phrasea\\Media\\Type\\Type::TYPE_IMAGE')
]
%}
<form style='padding:15px;' name="change-records-type" action="/prod/records/property/type/" method="POST">
<div class='well-small 'style="text-align:center;">
<select name='force_types' style="width:250px">
<option value="">{% trans 'Apply to all selected documents' %}</option>
{% for option in typesEnum %}
<option value="{{ option }}">{{ option }}</option>
{% endfor %}
</select>
</div>
<input name="lst" type="hidden" value="{{ records.serializedList() }}"/>
{% for sbasId,databoxTypes in recordsType %}
<h2 style="text-align:center;" clas="header">{{ sbasId|sbas_names(app) }}</h2>
{% for currentType, recordsByType in databoxTypes %}
<ul class="thumbnails" style="margin:20px auto;">
{% for record in recordsByType %}
<li class="span2">
<div class="thumbnail" style='min-height:205px'>
{{ thumbnail.format(record.get_thumbnail(), 160, 120, '', false, false) }}
<div class="caption">
<h5>{{ record.get_title() }}</h5>
<p>
<select name="types[{{record.get_serialize_key()}}]" style='width:100%'>
{% for option in typesEnum %}
<option value="{{ option }}" {{ record.is_grouping() ? "disabled='disabled'": "" }} {{ option == record.get_type() ? "selected='selected'" : '' }}>{{ option }}</option>
{% endfor %}
</select>
</p>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
<div class="form-actions">
<button type="button" class="btn btn-primary submiter">{% trans "Apply changes" %}</button>
<button type="button" class="btn cancel">{% trans "Cancel" %}</button>
</div>
</form>
<script type="text/javascript">
var $dialog = p4.Dialog.get(1);
var $dialogBox = $dialog.getDomElement();
$("button.submiter", $dialogBox).bind("click", function(){
var $this = $(this);
var form = $(this).closest("form");
$.ajax({
type: form.attr("method"),
url: form.attr("action"),
data: form.serializeArray(),
dataType: 'json',
beforeSend:function(){
$this.attr("disabled", true);
//@todo add loader
},
success: function(data){
$dialog.Close();
},
complete: function(){
$this.attr("disabled", false);
}
});
});
</script>

View File

@@ -10,15 +10,40 @@ class PropertyTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
protected $client; protected $client;
/** /**
* @covers Alchemy\Phrasea\Controller\Prod\Record\Property::displayProperty * @covers Alchemy\Phrasea\Controller\Prod\Record\Property::displayStatusProperty
*/ */
public function testDisplayProperty() public function testDisplayStatusProperty()
{ {
$property = new Property(); $property = new Property();
$request = Request::create('/prod/records/property/', 'GET', array( $request = Request::create('/prod/records/property/', 'GET', array(
'lst' => implode(';', array(self::$DI['record_no_access']->get_serialize_key(), self::$DI['record_1']->get_serialize_key(), self::$DI['record_4']->get_serialize_key())) 'lst' => implode(';', array(self::$DI['record_no_access']->get_serialize_key(), self::$DI['record_1']->get_serialize_key(), self::$DI['record_4']->get_serialize_key()))
), array(), array(), array('HTTP_X-Requested-With' => 'XMLHttpRequest')); ), array(), array(), array('HTTP_X-Requested-With' => 'XMLHttpRequest'));
$response = $property->displayProperty(self::$DI['app'], $request); $response = $property->displayStatusProperty(self::$DI['app'], $request);
$this->assertTrue($response->isOk());
}
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
* @covers Alchemy\Phrasea\Controller\Prod\Record\Property::displayStatusProperty
*/
public function testDisplayStatusPropertyNotXMLHTTPRequets()
{
$property = new Property();
$request = Request::create('/prod/records/property/', 'GET');
$property->displayStatusProperty(self::$DI['app'], $request);
unset($property, $request);
}
/**
* @covers Alchemy\Phrasea\Controller\Prod\Record\Property::displayTypeProperty
*/
public function testDisplayTypeProperty()
{
$property = new Property();
$request = Request::create('/prod/records/property/', 'GET', array(
'lst' => implode(';', array(self::$DI['record_no_access']->get_serialize_key(), self::$DI['record_1']->get_serialize_key(), self::$DI['record_4']->get_serialize_key()))
), array(), array(), array('HTTP_X-Requested-With' => 'XMLHttpRequest'));
$response = $property->displayTypeProperty(self::$DI['app'], $request);
$this->assertTrue($response->isOk()); $this->assertTrue($response->isOk());
} }
@@ -26,18 +51,18 @@ class PropertyTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
* @expectedException \Symfony\Component\HttpKernel\Exception\HttpException * @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
* @covers Alchemy\Phrasea\Controller\Prod\Record\Property::displayProperty * @covers Alchemy\Phrasea\Controller\Prod\Record\Property::displayProperty
*/ */
public function testDisplayPropertyNotXMLHTTPRequets() public function testDisplayTypePropertyNotXMLHTTPRequets()
{ {
$property = new Property(); $property = new Property();
$request = Request::create('/prod/records/property/', 'GET'); $request = Request::create('/prod/records/property/', 'GET');
$property->displayProperty(self::$DI['app'], $request); $property->displayTypeProperty(self::$DI['app'], $request);
unset($property, $request); unset($property, $request);
} }
/** /**
* @covers Alchemy\Phrasea\Controller\Prod\Record\Property::changeStatus * @covers Alchemy\Phrasea\Controller\Prod\Record\Property::changeStatus
*/ */
public function testChangeSTatus() public function testChangeStatus()
{ {
$property = new Property(); $property = new Property();
$request = Request::create('/prod/records/property/status/', 'POST', array( $request = Request::create('/prod/records/property/status/', 'POST', array(