Port PHRAS-1593

This commit is contained in:
Mike Ng
2017-11-24 16:22:31 +04:00
parent 79493d9dca
commit a79b72fbaf
5 changed files with 230 additions and 149 deletions

View File

@@ -1,3 +1,4 @@
{
"directory" : "www/bower_components"
"directory" : "www/bower_components",
"timeout": 1200000
}

View File

@@ -23,43 +23,70 @@ class ShareController extends Controller
*/
public function shareRecord($base_id, $record_id)
{
$outputVars = [
'isAvailable' => false,
'preview' => [
'permalinkUrl' => '',
'permaviewUrl' => '',
'embedUrl' => '',
'width' => '',
'height' => ''
]
];
$record = new \record_adapter($this->app, \phrasea::sbasFromBas($this->app, $base_id), $record_id);
if (!$this->getAclForUser()->has_access_to_subdef($record, 'preview')) {
$this->app->abort(403);
//get list of subdefs
$subdefs = $record->get_subdefs();
$databoxSubdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType());
$acl = $this->getAclForUser();
$subdefList = [];
$defaultKey = null;
foreach ($subdefs as $subdef) {
$subdefName = $subdef->get_name();
if ($subdefName == 'document') {
if (!$acl->has_right_on_base($record->getBaseId(), \ACL::CANDWNLDHD)) {
continue;
}
$label = $this->app->trans('prod::tools: document');
}
elseif ($databoxSubdefs->hasSubdef($subdefName)) {
if (!$acl->has_access_to_subdef($record, $subdefName)) {
continue;
}
$label = $databoxSubdefs->getSubdef($subdefName)->get_label($this->app['locale']);
}
else {
// this subdef does no exists anymore in databox structure ?
continue; // don't publish it
}
$value = $subdef->get_name();
$preview = $record->get_subdef($value);
$defaultKey = $value; // will set a default option if neither preview,thumbnail or document is present
$preview = $record->get_preview();
if (null !== $previewLink = $preview->get_permalink()) {
$permalinkUrl = $previewLink->get_url();
if ( ($previewLink = $preview->get_permalink()) !== null ) {
$permalinkUrl = $previewLink->get_url()->__toString();
$permaviewUrl = $previewLink->get_page();
$previewWidth = $preview->get_width();
$previewHeight = $preview->get_height();
$embedUrl = $this->app->url('alchemy_embed_view', ['url' => (string)$permalinkUrl]);
$outputVars = [
'isAvailable' => true,
'preview' => [
$previewData = [
'label' => $label,
'permalinkUrl' => $permalinkUrl,
'permaviewUrl' => $permaviewUrl,
'embedUrl' => $embedUrl,
'width' => $previewWidth,
'height' => $previewHeight
]
];
$subdefList[$value] = $previewData;
}
}
// candidates as best default selected option
foreach(["preview", "thumbnail", "document"] as $k) {
if (array_key_exists($k, $subdefList)) {
$defaultKey = $k;
break;
}
}
// if no subdef was sharable, subdefList is empty and defaultKey is null
// the twig MUST handle that
$outputVars = [
'isAvailable' => !empty($subdefList),
'subdefList' => $subdefList,
'defaultKey' => $defaultKey
];
return $this->renderResponse('prod/Share/record.html.twig', $outputVars);
}

View File

@@ -47,10 +47,22 @@ class Share implements ControllerProviderInterface, ServiceProviderInterface
$controllers->get('/record/{base_id}/{record_id}/', 'controller.prod.share:shareRecord')
->before(function (Request $request) use ($app, $firewall) {
$socialTools = $app['conf']->get(['registry', 'actions', 'social-tools']);
if($socialTools === "all") {
return;
}
elseif($socialTools === "none") {
$app->abort(403, 'social tools disabled');
}
elseif($socialTools === "publishers") {
$firewall->requireRightOnSbas(
\phrasea::sbasFromBas($app, $request->attributes->get('base_id')),
\ACL::BAS_CHUPUB
);
}
else {
throw new \Exception("bad value \"" . $socialTools . "\" for social tools");
}
})
->bind('share_record');

View File

@@ -1,17 +1,29 @@
{% if not isAvailable %}
<p>{{ 'No permalink available.' | trans }}</p>
{% else %}
{% if preview.permalinkUrl is not empty %}
{% if subdefList is not empty %}
{% set defKey = defaultKey %}
<div id="share">
<div id="tweet" class="well-large">
<p>
<a href="http://www.twitter.com/home/?status={{ preview.permaviewUrl }}" target="_blank">
<a href="#" id="advance-share">{{ 'share::share-record: advance' | trans }}</a>
<span id="shared-def">
{{ 'share::share-record: select-shared-def' | trans }}
<select name="resource_type" id="resource_type_sel" class="input-small">
{% for key,value in subdefList %}
<option value={{ key }} {% if key == defKey %}selected{% endif %}>{{ value.label }}</option>
{% endfor %}
</select>
</span>
</p>
<p>
<a id="twitter-link" href="http://www.twitter.com/home/?status={{ subdefList[defKey].permaviewUrl }}" target="_blank">
<img src="/assets/common/images/icons/twitter.png" title="share this on twitter" style="width:25px;vertical-align:middle;padding:0 5px;"/>
{% trans %}Send to Twitter{% endtrans %}
</a>
</p>
<p>
<a href="http://www.facebook.com/sharer.php?u={{ preview.permaviewUrl }}" target="_blank">
<a id="facebook-link" href="http://www.facebook.com/sharer.php?u={{ subdefList[defKey].permaviewUrl }}" target="_blank">
<img src="/assets/common/images/icons/facebook.png" title="share on facebook" style="width:25px;vertical-align:middle;padding:0 5px;"/>
{% trans %}Send to Facebook{% endtrans %}
</a>
@@ -19,20 +31,20 @@
<form action="#">
<div class="form-group clearfix">
<label>{% trans %}Resource URL{% endtrans %}</label>
<input class="input-block-level" readonly="readonly" type="text" value="{{ preview.permalinkUrl }}"
<label style="display:inline-block;">{% trans %}Resource URL{% endtrans %}</label>
<input class="input-block-level" readonly="readonly" type="text" value="{{ subdefList[defKey].permalinkUrl }}"
id="permalinkUrl"/>
<p class="pull-right">
<a href="{{ preview.permalinkUrl }}" target="_blank">{{ 'previewLinkLabel' | trans }}</a> &nbsp;&nbsp;
<a id="permalinkUrl-link" href="{{ subdefList[defKey].permalinkUrl }}" target="_blank">{{ 'previewLinkLabel' | trans }}</a> &nbsp;&nbsp;
<a href="#" class="" id="permalinkUrlCopy">{{ 'copyClipboardLabel' | trans }}</a>
</p>
</div>
<div class="form-group clearfix">
<label>{% trans %}Detailed view URL{% endtrans %}</label>
<input class="input-block-level" readonly="readonly" type="text" value="{{ preview.permaviewUrl }}" id="permaviewUrl"/>
<input class="input-block-level" readonly="readonly" type="text" value="{{ subdefList[defKey].permaviewUrl }}" id="permaviewUrl"/>
<p class="pull-right">
<a href="{{ preview.permaviewUrl }}" target="_blank">{{ 'previewLinkLabel' | trans }}</a> &nbsp;&nbsp;
<a id="permaviewUrl-link" href="{{ subdefList[defKey].permaviewUrl }}" target="_blank">{{ 'previewLinkLabel' | trans }}</a> &nbsp;&nbsp;
<a href="#" class="" id="permaviewUrlCopy">{{ 'copyClipboardLabel' | trans }}</a>
</p>
</div>
@@ -41,18 +53,69 @@
<label>{% trans %}Embed code{% endtrans %}</label>
{% spaceless %}
<textarea class="input-block-level" rows="4" readonly="true" id="embedRecordUrl">
<iframe width="{{ preview.width }}" height="{{ preview.height }}" src="{{ preview.embedUrl }}" frameborder="0" allowfullscreen></iframe>
<iframe width="{{ subdefList[defKey].width }}" height="{{ subdefList[defKey].height }}" src="{{ subdefList[defKey].embedUrl }}" frameborder="0" allowfullscreen></iframe>
</textarea>
{% endspaceless %}
<p class="pull-right">
<a href="{{ preview.embedUrl }}" target="_blank">{{ 'previewLinkLabel' | trans }}</a> &nbsp;&nbsp;
<a id="embedRecordUrl-link" href="{{ subdefList[defKey].embedUrl }}" target="_blank">{{ 'previewLinkLabel' | trans }}</a> &nbsp;&nbsp;
<a href="#" class="" id="embedCopy">{{ 'copyClipboardLabel' | trans }}</a>
</p>
</div>
{#{% endif %}#}
</form>
</p>
</div>
<script language="javascript">
// var subdefListObj = JSON.parse(subdefList); // to convert json into a javascript object
$(document).ready(function(){
var subdefList = JSON.parse('{{ subdefList | json_encode | escape('js') }}');
$('#resource_type_sel').on('change', function() {
assignPermalinks(this.value);
});
$('input.ui-state-default').hover(
function(){$(this).addClass('ui-state-hover');},
function(){$(this).removeClass('ui-state-hover');}
);
$('#permalinkUrlCopy').on('click', function(event) {
event.preventDefault();
return copyElContentClipboard('permalinkUrl');
});
$('#permaviewUrlCopy').on('click', function(event) {
event.preventDefault();
return copyElContentClipboard('permaviewUrl');
});
$('#embedCopy').on('click', function(event) {
event.preventDefault();
return copyElContentClipboard('embedRecordUrl');
});
$('#advance-share').on('click', function() {
$('#shared-def').show();
});
var copyElContentClipboard = function(elId) {
var copyEl = document.getElementById(elId);
copyEl.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('unable to copy');
}
}
var assignPermalinks = function(resourceType) {
$('#twitter-link').attr('href', 'http://www.twitter.com/home/?status=' + subdefList[resourceType].permaviewUrl);
$('#facebook-link').attr('href', 'http://www.facebook.com/sharer.php?u=' + subdefList[resourceType].permaviewUrl);
$('#permalinkUrl').val(subdefList[resourceType].permalinkUrl);
$('#permalinkUrl-link').attr('href', subdefList[resourceType].permalinkUrl);
$('#permaviewUrl').val(subdefList[resourceType].permaviewUrl);
$('#permaviewUrl-link').attr('href', subdefList[resourceType].permaviewUrl);
var html = '<iframe width="' + subdefList[resourceType].width
+ '" height="' + subdefList[resourceType].height + '" src="' + subdefList[resourceType].embedUrl + '" frameborder="0" allowfullscreen></iframe>';
$('#embedRecordUrl').val(html);
$('#embedRecordUrl-link').attr('href', subdefList[resourceType].embedUrl);
}
});
</script>
{% else %}
<div>{{ 'No URL available' | trans }}</div>
{% endif %}

View File

@@ -2,6 +2,7 @@
namespace Alchemy\Tests\Phrasea\Controller\Prod;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Controller\Prod\ShareController;
use Alchemy\Phrasea\ControllerProvider\Prod\Share;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -19,86 +20,63 @@ class ShareTest extends \PhraseanetAuthenticatedWebTestCase
/**
* @covers Alchemy\Phrasea\Controller\Prod\Share::shareRecord
* @covers Alchemy\Phrasea\Controller\Prod\Share::connect
* @covers Alchemy\Phrasea\Controller\Prod\Share::call
*/
public function testMountedRouteSlash()
public function testRouteSlashALL()
{
$url = sprintf('/prod/share/record/%d/%d/', self::$DI['record_1']->get_base_id(), self::$DI['record_1']->get_record_id());
self::$DI['client']->request('GET', $url);
$this->assertTrue(self::$DI['client']->getResponse()->isOk());
$this->_RouteSlash("all", [0=>true, 1=>true, 2=>true, 3=>true]);
}
/**
* @covers Alchemy\Phrasea\Controller\Prod\Share::shareRecord
* @covers Alchemy\Phrasea\Controller\Prod\Share::connect
*/
public function testRouteSlash()
public function testRouteSlashPublishers()
{
$this->_RouteSlash("publishers", [0=>false, 1=>true, 2=>false, 3=>true]);
}
public function testRouteSlashNone()
{
$this->_RouteSlash("none", [0=>false, 1=>false, 2=>false, 3=>false]);
}
private function _RouteSlash($setting, $expected)
{
$app = $this->getApplication();
$_conf = $app['conf'];
$app['conf'] = $this->getMockBuilder('Alchemy\Phrasea\Core\Configuration\PropertyAccess')
->disableOriginalConstructor()
->getMock();
$app['conf']
->expects($this->any())
->method('get')
->will($this->returnCallback(function ($param, $default) use ($_conf, $setting) {
switch ($param) {
case ['registry', 'actions', 'social-tools']:
return $setting;
}
return $_conf->get($param, $default);
}));
$result = [];
foreach($expected as $flags=>$v) {
$stubbedACL = $this->stubACL();
//has_right_on_base return true
$stubbedACL->expects($this->once())
// "has_right_on_sbas" IS checked by the route->before(), the url will return 403
$stubbedACL->expects($this->any())
->method('has_right_on_sbas')
->will($this->returnValue(true));
//has_access_to_subdef return true
$stubbedACL->expects($this->once())
->will($this->returnValue(($flags & 1) ? true:false));
// but "has_access_to_subdef" IS NOT checked (the url will return a 200 with a message "no subdef to share")
$stubbedACL->expects($this->any())
->method('has_access_to_subdef')
->will($this->returnValue(true));
->will($this->returnValue(($flags & 2) ? true:false));
$url = sprintf('/prod/share/record/%d/%d/', self::$DI['record_1']->get_base_id(), self::$DI['record_1']->get_record_id());
self::$DI['client']->request('GET', $url);
$this->assertTrue(self::$DI['client']->getResponse()->isOk());
$result[$flags] = self::$DI['client']->getResponse()->isOk();
}
$this->assertEquals($expected, $result);
}
/**
* @covers Alchemy\Phrasea\Controller\Prod\Share::shareRecord
*/
public function testShareRecord()
{
$share = new ShareController(self::$DI['app']);
/** @var \record_adapter $record_1 */
$record_1 = self::$DI['record_1'];
$response = $share->shareRecord($record_1->getBaseId(), $record_1->getRecordId());
$this->assertTrue($response->isOk());
}
/**
* @covers Alchemy\Phrasea\Controller\Prod\Share::shareRecord
*/
public function testShareRecordBadAccess()
{
$share = new ShareController(self::$DI['app']);
$stubbedACL = $this->getMockBuilder('\ACL')
->disableOriginalConstructor()
->getMock();
//has_access_to_subdef return false
$stubbedACL->expects($this->once())
->method('has_access_to_subdef')
->will($this->returnValue(false));
$aclProvider = $this->getMockBuilder('Alchemy\Phrasea\Authentication\ACLProvider')
->disableOriginalConstructor()
->getMock();
$aclProvider->expects($this->any())
->method('get')
->will($this->returnValue($stubbedACL));
self::$DI['app']['acl'] = $aclProvider;
try {
$share->shareRecord(self::$DI['record_1']->get_base_id(), self::$DI['record_1']->get_record_id());
} catch (HttpException $exception) {
$this->assertEquals(403, $exception->getStatusCode());
return;
}
$this->fail('An access denied exception should have been thrown.');
}
}