Merge pull request #712 from romainneutron/master

Merge 3.8 in master
This commit is contained in:
Romain Neutron
2013-10-25 04:49:40 -07:00
24 changed files with 288 additions and 55 deletions

View File

@@ -22,7 +22,8 @@
"zxcvbn": "https://github.com/lowe/zxcvbn.git",
"geonames-server-jquery-plugin": "~0.2",
"swfobject": "latest",
"tinymce": "~4.0"
"tinymce": "~4.0",
"jquery-galleria": "1.2.9"
},
"devDependencies": {
"mocha": "latest",

View File

@@ -31,14 +31,24 @@ class Permalink extends AbstractDelivery
$that = $this;
$deliverPermaview = function($sbas_id, $record_id, $token, $subdef, PhraseaApplication $app) {
$retrieveRecord = function ($app, $databox, $token, $record_id, $subdef) {
if (in_array($subdef, array(\databox_subdef::CLASS_PREVIEW, \databox_subdef::CLASS_THUMBNAIL)) && $app['EM']->getRepository('Entities\FeedItem')->isRecordInPublicFeed($app, $databox->get_sbas_id(), $record_id)) {
$record = $databox->get_record($record_id);
} else {
$record = \media_Permalink_Adapter::challenge_token($app, $databox, $token, $record_id, $subdef);
if (!($record instanceof \record_adapter)) {
throw new NotFoundHttpException('Wrong token.');
}
}
return $record;
};
$deliverPermaview = function($sbas_id, $record_id, $token, $subdef, PhraseaApplication $app) use ($retrieveRecord) {
$databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id);
$record = \media_Permalink_Adapter::challenge_token($app, $databox, $token, $record_id, $subdef);
if (!$record instanceof \record_adapter) {
throw new NotFoundHttpException('bad luck');
}
$record = $retrieveRecord($app, $databox, $token, $record_id, $subdef);
$params = array(
'subdef_name' => $subdef
@@ -51,13 +61,10 @@ class Permalink extends AbstractDelivery
return $app['twig']->render('overview.html.twig', $params);
};
$deliverPermalink = function(PhraseaApplication $app, $sbas_id, $record_id, $token, $subdef) use ($that) {
$deliverPermalink = function(PhraseaApplication $app, $sbas_id, $record_id, $token, $subdef) use ($that, $retrieveRecord) {
$databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id);
$record = \media_Permalink_Adapter::challenge_token($app, $databox, $token, $record_id, $subdef);
if (!($record instanceof \record_adapter)) {
throw new NotFoundHttpException('bad luck');
}
$record = $retrieveRecord($app, $databox, $token, $record_id, $subdef);
$watermark = $stamp = false;
@@ -106,15 +113,11 @@ class Permalink extends AbstractDelivery
return $response;
};
$controllers->get('/v1/{sbas_id}/{record_id}/caption/', function(PhraseaApplication $app, Request $request, $sbas_id, $record_id) {
$controllers->get('/v1/{sbas_id}/{record_id}/caption/', function(PhraseaApplication $app, Request $request, $sbas_id, $record_id) use ($retrieveRecord) {
$token = $request->query->get('token');
$databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id);
$record = \media_Permalink_Adapter::challenge_token($app, $databox, $token, $record_id, 'thumbnail');
if (null === $record) {
throw new NotFoundHttpException("Caption not found");
}
$record = $retrieveRecord($app, $databox, $token, $record_id, \databox_subdef::CLASS_THUMBNAIL);
$caption = $record->get_caption();
return new Response($caption->serialize(\caption_record::SERIALIZE_JSON), 200, array("Content-Type" => 'application/json'));

View File

@@ -93,7 +93,7 @@ class Feed implements ControllerProviderInterface
$app['EM']->persist($feed);
$app['EM']->flush();
$app['events-manager']->trigger('__FEED_ENTRY_CREATE__', array('entry_id' => $entry->getId()), $entry);
$app['events-manager']->trigger('__FEED_ENTRY_CREATE__', array('entry_id' => $entry->getId(), 'notify_email' => (Boolean) $request->request->get('notify')), $entry);
$datas = array('error' => false, 'message' => false);

View File

@@ -49,7 +49,22 @@ class Login implements ControllerProviderInterface
{
public static function getDefaultTemplateVariables(Application $app)
{
$items = array();
foreach($app['EM']->getRepository('Entities\FeedItem')->loadLatest($app, 20) as $item) {
$record = $item->getRecord($app);
$preview = $record->get_subdef('preview');
$permalink = $preview->get_permalink();
$items[] = array(
'record' => $record,
'preview' => $preview,
'permalink' => $permalink
);
}
return array(
'last_publication_items' => $items,
'instance_title' => $app['phraseanet.registry']->get('GV_homeTitle'),
'has_terms_of_use' => $app->hasTermsOfUse(),
'meta_description' => $app['phraseanet.registry']->get('GV_metaDescription'),

View File

@@ -2,6 +2,7 @@
namespace Repositories;
use Alchemy\Phrasea\Application;
use Doctrine\ORM\EntityRepository;
/**
@@ -12,4 +13,73 @@ use Doctrine\ORM\EntityRepository;
*/
class FeedItemRepository extends EntityRepository
{
/**
* Checks if a record is published in a public feed.
*
* @param Application $app
* @param integer $sbas_id
* @param integer $record_id
*
* @return Boolean
*/
public function isRecordInPublicFeed(Application $app, $sbas_id, $record_id)
{
$dql = 'SELECT i
FROM Entities\FeedItem i
JOIN i.entry e
JOIN e.feed f
WHERE i.sbasId = :sbas_id
AND i.recordId = :record_id
AND f.public = true';
$query = $this->_em->createQuery($dql);
$query->setParameters(array('sbas_id' => $sbas_id, 'record_id' => $record_id));
return count($query->getResult()) > 0;
}
/**
* Gets latest items from public feeds.
*
* @param Application $app
* @param integer $nbItems
*
* @return FeedItem[] An array of FeedItem
*/
public function loadLatest(Application $app, $nbItems = 20)
{
$execution = 0;
$items = array();
do {
$dql = 'SELECT i
FROM Entities\FeedItem i
JOIN i.entry e
JOIN e.feed f
WHERE f.public = true ORDER BY i.createdOn DESC';
$query = $this->_em->createQuery($dql);
$query
->setFirstResult((integer) $nbItems * $execution)
->setMaxResults((integer) $nbItems);
$result = $query->getResult();
foreach($result as $item) {
if (null !== $preview = $item->getRecord($app)->get_subdef('preview')) {
if (null !== $permalink = $preview->get_permalink()) {
$items[] = $item;
if (count($items) >= $nbItems) {
break;
}
}
}
}
$execution++;
} while (count($items) < $nbItems && count($result) !== 0);
return $items;
}
}

View File

@@ -119,6 +119,11 @@ class ACL implements cache_cacheableInterface
return $this;
}
public function set_app(Application $app)
{
$this->app = $app;
}
/**
* Check if a hd grant has been received for a record
*
@@ -253,6 +258,10 @@ class ACL implements cache_cacheableInterface
$granted = true;
}
if (false === $granted && $this->app['EM']->getRepository('Entities\FeedItem')->isRecordInPublicFeed($this->app, $record->get_sbas_id(), $record->get_record_id())) {
$granted = true;
}
return $granted;
}

View File

@@ -327,6 +327,8 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
self::$_instance[$id] = new self($id, $app);
$app['phraseanet.appbox']->set_data_to_cache(self::$_instance[$id], '_user_' . $id);
}
} else {
self::$_instance[$id]->set_app($app);
}
return array_key_exists($id, self::$_instance) ? self::$_instance[$id] : false;
@@ -349,6 +351,9 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
protected function set_app(Application $app)
{
$this->app = $app;
if (null !== $this->ACL) {
$this->ACL->set_app($app);
}
}
/**
@@ -1074,6 +1079,8 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
}
}
$this->load_notifications_preferences($this->app);
return $this;
}
@@ -1090,22 +1097,18 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
}
}
}
$this->notification_preferences_loaded = true;
}
protected $notifications_preferences_loaded = false;
public function get_notifications_preference(Application $app, $notification_id)
{
if (!$this->notifications_preferences_loaded)
$this->load_notifications_preferences($app);
$this->load_preferences($app);
return $this->_prefs['notification_' . $notification_id];
return isset($this->_prefs['notification_' . $notification_id]) ? $this->_prefs['notification_' . $notification_id] : null;
}
public function set_notification_preference(Application $app, $notification_id, $value)
{
if (!$this->notifications_preferences_loaded)
$this->load_notifications_preferences($app);
$this->load_preferences($app);
return $this->_prefs['notification_' . $notification_id] = $value ? '1' : '0';
}

View File

@@ -285,6 +285,7 @@ class databox_field implements cache_cacheableInterface
$databox->set_data_to_cache(self::$_instance[$instance_id], $cache_key);
}
}
self::$_instance[$instance_id]->app = $app;
return self::$_instance[$instance_id];
}

View File

@@ -45,7 +45,8 @@ class eventsmanager_notify_feed extends eventsmanager_notifyAbstract
public function fire($event, $params, &$entry)
{
$params = array(
'entry_id' => $entry->getId()
'entry_id' => $entry->getId(),
'notify_email' => $params['notify_email'],
);
$dom_xml = new DOMDocument('1.0', 'UTF-8');
@@ -91,7 +92,7 @@ class eventsmanager_notify_feed extends eventsmanager_notifyAbstract
/* @var $user_to_notif \User_Adapter */
$mailed = false;
if ($this->shouldSendNotificationFor($user_to_notif->get_id())) {
if ($params['notify_email'] && $this->shouldSendNotificationFor($user_to_notif->get_id())) {
$readyToSend = false;
try {
$token = $this->app['tokens']->getUrlToken(

View File

@@ -26,7 +26,7 @@ abstract class eventsmanager_notifyAbstract extends eventsmanager_eventAbstract
protected function get_prefs($class, $usr_id)
{
$user = User_Adapter::getInstance($usr_id, $this->app);
$pref = $user->getPrefs('notification_' . $class);
$pref = $user->get_notifications_preference($this->app, $class);
return null !== $pref ? $pref : 1;
}

View File

@@ -47,9 +47,7 @@ class LoadOneFeed extends AbstractFixture implements FixtureInterface
$feed->setTitle("test");
}
if (isset($this->public) && $this->public !== null) {
$feed->setIsPublic($this->public);
}
$feed->setIsPublic((Boolean) $this->public);
$feed->setSubtitle("description");

View File

@@ -348,7 +348,9 @@ return call_user_func_array(function(Application $app) {
'available' => array(
'DISPLAYx1' => _('Single image'),
'SCROLL' => _('Slide show'),
'COOLIRIS' => 'Cooliris'
'COOLIRIS' => 'Cooliris',
'CAROUSEL' => _('Carousel'),
'GALLERIA' => _('Gallery')
),
'required' => true
)

View File

@@ -0,0 +1,16 @@
<div id="carousel" class="carousel slide" style="height: 400px;">
{% set firstItem = true %}
<div class="carousel-inner">
{% for item in last_publication_items %}
<div class="{% if loop.first %}active {% endif %}item text-center">
<img src="{{ item['permalink'].get_url() }}" />
</div>
{% endfor %}
</div>
</div>
<script type="text/javascript">
require(["bootstrap"], function() {
$("#carousel").carousel();
});
</script>

View File

@@ -0,0 +1,25 @@
<div id="galleria" class="hidden">
{% for item in last_publication_items %}
<img src="{{ item['permalink'].get_url() }}" />
{% endfor %}
</div>
<script type="text/javascript">
requirejs.config({
paths: {
'galleria' : '../assets/jquery-galleria/src/galleria'
},
shim: {
'galleria': {
deps: ['jquery'],
exports: 'Galleria'
}
}
});
require(["jquery", "galleria"], function($, Galleria) {
Galleria.loadTheme('/assets/jquery-galleria/src/themes/classic/galleria.classic.js');
Galleria.run("#galleria");
$("#galleria").removeClass("hidden");
});
</script>

View File

@@ -123,9 +123,35 @@
{% include 'login/include/cooliris-content.html.twig' %}
{% elseif display_layout == "SCROLL" %}
{% include 'login/include/scroll-content.html.twig' %}
{% elseif display_layout == "CAROUSEL" %}
{% include 'login/include/carousel.html.twig' %}
{% elseif display_layout == "GALLERIA" %}
{% include 'login/include/galleria.html.twig' %}
{% endif %}
{% endblock %}
{% block header_stylesheet %}
{{ parent() }}
{% if display_layout == "CAROUSEL" %}
<style type="text/css">
#carousel {
height: 400px;
}
#carousel .item img {
margin: 0 auto;
max-height: 400px;
}
</style>
{% elseif display_layout == "GALLERIA" %}
<style type="text/css">
#galleria {
height: 400px;
</style>
{% endif %}
{% endblock %}
{% block scripts %}
{{ parent() }}
<script type="text/javascript" src="{{ path('minifier', {'f': 'scripts/apps/login/home/login.js'}) }}"></script>

View File

@@ -39,6 +39,8 @@
{% set title = publishing.basket().getName() %}
{% set desc = publishing.basket().getDescription() %}
{% endif %}
<input class="required_text checkbox" checked="checked" type="checkbox" name="notify" id="feed_add_notify" value="1" />
<label for="feed_add_notify">{% trans 'Notify users about this publication' %}</label><br/>
<label for="feed_add_title">{% trans 'publication : titre' %}</label>
<input class="required_text" type="text" name="title" id="feed_add_title" value="{{title}}" />
<label for="feed_add_subtitle">{% trans 'publication : sous titre' %}</label>
@@ -60,7 +62,7 @@
{% if feed.isPublic() %}
<img src="/skins/icons/ligth-on.png" title="{% trans 'This feed is public' %}"/>
{% endif %}
<input type="hidden" value="{{ feed.getId() }}"/>
<input type="hidden" name="feed_proposal[]" value="{{ feed.getId() }}"/>
</div>
{% endif %}
{% endfor %}

View File

@@ -2,7 +2,7 @@
namespace Alchemy\Tests\Phrasea\Application;
class ApplicationLightboxTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
class LightboxTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
{
protected $client;
@@ -311,21 +311,24 @@ class ApplicationLightboxTest extends \PhraseanetWebTestCaseAuthenticatedAbstrac
$this->assertObjectHasAttribute('error', $datas);
}
public function testAjaxSetRelease()
public function testAjaxSetReleaseWithRegularBasket()
{
$basket = $this->insertOneBasket();
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoValidationDone');
$crawler = self::$DI['client']->request('POST', '/lightbox/ajax/SET_RELEASE/' . $basket->getId() . '/');
$this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode());
$this->assertEquals('application/json', self::$DI['client']->getResponse()->headers->get('Content-type'));
$datas = json_decode(self::$DI['client']->getResponse()->getContent());
$this->assertTrue(is_object($datas), 'asserting good json datas');
$this->assertTrue($datas->error);
}
public function testAjaxSetReleaseWithRegularBasketWithValidation()
{
$validationBasket = $this->insertOneValidationBasket();
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoValidationDone');
foreach ($validationBasket->getElements() as $element) {
$element->getUserValidationDatas(self::$DI['app']['authentication']->getUser(), self::$DI['app'])->setAgreement(true);
break;

View File

@@ -5,7 +5,7 @@ namespace Alchemy\Tests\Phrasea\Application;
use Alchemy\Phrasea\Border\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class ApplicationOverviewTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
class OverviewTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
{
public function testDatafilesRouteAuthenticated()
{
@@ -61,6 +61,25 @@ class ApplicationOverviewTest extends \PhraseanetWebTestCaseAuthenticatedAbstrac
$this->assertForbiddenResponse(self::$DI['client']->getResponse());
}
public function testDatafilesRouteOnUnaccessibleRecordIsOkInPublicFeed()
{
$tmp = tempnam(sys_get_temp_dir(), 'testEtag');
copy(__DIR__ . '/../../../../files/cestlafete.jpg', $tmp);
$media = self::$DI['app']['mediavorus']->guess($tmp);
$file = new File(self::$DI['app'], $media, self::$DI['collection_no_access']);
$record = \record_adapter::createFromFile($file, self::$DI['app']);
$record->generate_subdefs($record->get_databox(), self::$DI['app']);
$item = $this->insertOneFeedItem(self::$DI['user'], true, 1, $record);
self::$DI['client']->request('GET', '/datafiles/' . $record->get_sbas_id() . '/' . $record->get_record_id() . '/preview/');
$this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode());
unlink($tmp);
}
public function testDatafilesRouteNotAuthenticatedUnknownSubdef()
{
self::$DI['app']['authentication']->closeAccount();
@@ -189,6 +208,16 @@ class ApplicationOverviewTest extends \PhraseanetWebTestCaseAuthenticatedAbstrac
$this->assertEquals(200, $response->getStatusCode());
}
public function testPermalinkRouteNotAuthenticatedIsOkInPublicFeed()
{
$record = $this->insertOneFeedItem(self::$DI['user'], true)->getRecord(self::$DI['app']);
self::$DI['app']['authentication']->closeAccount();
self::$DI['client']->request('GET', '/permalink/v1/' . $record->get_sbas_id() . '/' . $record->get_record_id() . '/preview/');
$this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode());
}
protected function get_a_permaviewBCcompatibility(array $headers = array())
{
$token = self::$DI['record_1']->get_preview()->get_permalink()->get_token();

View File

@@ -28,7 +28,7 @@ class FeedTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
$feeds = self::$DI['app']['EM']->getRepository('Entities\Feed')->getAllForUser(self::$DI['user']);
foreach ($feeds as $one_feed) {
if ($one_feed->isPublisher(self::$DI['user'])) {
$this->assertEquals(1, $crawler->filterXPath("//input[@value='" . $one_feed->getId() . "']")->count());
$this->assertEquals(1, $crawler->filterXPath("//input[@value='" . $one_feed->getId() . "' and @name='feed_proposal[]']")->count());
}
}
}
@@ -46,6 +46,7 @@ class FeedTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
$feed = $this->insertOneFeed(self::$DI['user']);
$params = array(
"feed_id" => $feed->getId()
, "notify" => 1
, "title" => "salut"
, "subtitle" => "coucou"
, "author_name" => "robert"

View File

@@ -14,7 +14,7 @@ main:
driver: pdo_sqlite
path: '/tmp/db.sqlite'
charset: UTF8
api-timers: true
api-timers: false
cache:
type: MemcacheCache
options:

View File

@@ -14,7 +14,7 @@ main:
driver: pdo_sqlite
path: '/tmp/db.sqlite'
charset: UTF8
api-timers: true
api-timers: false
cache:
type: MemcacheCache
options:

View File

@@ -6,6 +6,9 @@ use Alchemy\Phrasea\Border\File;
use Doctrine\Common\DataFixtures\Loader;
use Entities\AggregateToken;
use Entities\Feed;
use Entities\FeedEntry;
use Entities\FeedItem;
use Entities\FeedPublisher;
use Entities\FeedToken;
use Entities\User;
use Silex\WebTestCase;
@@ -360,10 +363,10 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase
*
* @return \Entities\FeedEntry
*/
protected function insertOneFeedEntry(\User_Adapter $user)
protected function insertOneFeedEntry(\User_Adapter $user, $public = false)
{
try {
$feed = $this->insertOneFeed($user);
$feed = $this->insertOneFeed($user, '', $public);
$em = self::$DI['app']['EM'];
@@ -433,22 +436,31 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase
*
* @return \Entities\FeedItem
*/
protected function insertOneFeedItem(\User_Adapter $user)
protected function insertOneFeedItem(\User_Adapter $user, $public = false, $qty = 1, \record_adapter $record = null)
{
try {
$entry = $this->insertOneFeedEntry($user);
$item = new \Entities\FeedItem();
$item->setEntry($entry);
$item->setRecordId(self::$DI['record_1']->get_record_id());
$item->setSbasId(self::$DI['record_1']->get_sbas_id());
$entry->addItem($item);
$em = self::$DI['app']['EM'];
$entry = $this->insertOneFeedEntry($user, $public);
for ($i = 0; $i < $qty; $i++) {
$item = new \Entities\FeedItem();
$item->setEntry($entry);
if (null === $record) {
$actual = self::$DI['record_'.($i+1)];
} else {
$actual = $record;
}
$item->setRecordId($actual->get_record_id());
$item->setSbasId($actual->get_sbas_id());
$item->setEntry($entry);
$entry->addItem($item);
$em->persist($item);
}
$em->persist($entry);
$em->persist($item);
$em->flush();
} catch (\Exception $e) {

View File

@@ -3203,6 +3203,14 @@ dans l'onglet thesaurus : arbres, menus contextuels
margin: 3px 0 10px 0;
}
#modal_feed form label {
display:inline;
}
#modal_feed form input.checkbox {
width: 20px;
}
#modal_feed form textarea {
height: 60px;
resize: none;

View File

@@ -3283,6 +3283,14 @@ dans l'onglet thesaurus : arbres, menus contextuels
margin: 3px 0 10px 0;
}
#modal_feed form label {
display:inline;
}
#modal_feed form input.checkbox {
width: 20px;
}
#modal_feed form textarea {
height: 60px;
resize: none;