Merge branch '3.8'

Conflicts:
	bower.json
	lib/Alchemy/Phrasea/Controller/Prod/Feed.php
	lib/classes/Feed/Entry/Adapter.php
	lib/classes/Feed/Entry/Item.php
	lib/classes/eventsmanager/notify/feed.php
	tests/classes/Feed/Entry/Feed_Entry_ItemTest.php
This commit is contained in:
Romain Neutron
2013-10-24 14:05:04 +02:00
21 changed files with 375 additions and 29 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,26 @@ 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 (\databox_subdef::CLASS_THUMBNAIL === $subdef) {
$record = $databox->get_record($record_id);
} elseif (\databox_subdef::CLASS_PREVIEW === $subdef && $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 +63,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 +115,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, 'caption');
$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->get_record();
$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,74 @@ 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));
$query->useResultCache(false);
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

@@ -253,6 +253,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

@@ -1074,6 +1074,8 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
}
}
$this->load_notifications_preferences($this->app);
return $this;
}
@@ -1097,7 +1099,7 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
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];
}
@@ -1105,7 +1107,7 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
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

@@ -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

@@ -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

@@ -61,6 +61,84 @@ class ApplicationOverviewTest extends \PhraseanetWebTestCaseAuthenticatedAbstrac
$this->assertForbiddenResponse(self::$DI['client']->getResponse());
}
public function testIs_record_in_public_feed()
{
$feed = $this->createFeed();
$this->setFeedIsPublic($feed, true);
$entry = $this->createEntry($feed);
$this->addItem($entry, self::$DI['record_1']);
$this->assertTrue(self::$DI['app']['EM']->getRepository('Entities\FeedItem')->isRecordInPublicFeed(self::$DI['app'], self::$DI['record_1']->get_sbas_id(), self::$DI['record_1']->get_record_id()));
$this->setFeedIsPublic($feed, false);
$this->assertFalse(self::$DI['app']['EM']->getRepository('Entities\FeedItem')->isRecordInPublicFeed(self::$DI['app'], self::$DI['record_1']->get_sbas_id(), self::$DI['record_1']->get_record_id()));
}
public function testLoadLatestItems()
{
$feed = $this->createFeed();
$this->setFeedIsPublic($feed, true);
$entry = $this->createEntry($feed);
foreach(range(1, 2) as $i) {
$this->addItem($entry, self::$DI['record_'.$i]);
}
$this->assertCount(2, self::$DI['app']['EM']->getRepository('Entities\FeedItem')->loadLatest(self::$DI['app'], 20));
}
public function testLoadLatestItemsLessItems()
{
$feed = $this->createFeed();
$this->setFeedIsPublic($feed, true);
$entry = $this->createEntry($feed);
foreach(range(1, 2) as $i) {
$this->addItem($entry, self::$DI['record_'.$i]);
}
$this->assertCount(1, self::$DI['app']['EM']->getRepository('Entities\FeedItem')->loadLatest(self::$DI['app'], 1));
}
public function testLoadLatestItemsNoPublic()
{
$feed = $this->createFeed();
$entry = $this->createEntry($feed);
foreach(range(1, 2) as $i) {
$this->addItem($entry, self::$DI['record_'.$i]);
}
$this->assertCount(0, self::$DI['app']['EM']->getRepository('Entities\FeedItem')->loadLatest(self::$DI['app'], 20));
}
public function testDatafilesRouteNotAuthenticatedIsOkInPublicFeed()
{
$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']);
$feed = $this->createFeed();
$this->setFeedIsPublic($feed, true);
$entry = $this->createEntry($feed);
$this->addItem($entry, $record);
self::$DI['record_1']->move_to_collection(self::$DI['collection_no_access'], self::$DI['app']['phraseanet.appbox']);
self::$DI['client']->request('GET', '/datafiles/' . $record->get_sbas_id() . '/' . $record->get_record_id() . '/preview/');
$this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode());
self::$DI['record_1']->move_to_collection(self::$DI['collection'], self::$DI['app']['phraseanet.appbox']);
unlink($tmp);
}
public function testDatafilesRouteNotAuthenticatedUnknownSubdef()
{
self::$DI['app']['authentication']->closeAccount();
@@ -189,6 +267,19 @@ class ApplicationOverviewTest extends \PhraseanetWebTestCaseAuthenticatedAbstrac
$this->assertEquals(200, $response->getStatusCode());
}
public function testPermalinkRouteNotAuthenticatedIsOkInPublicFeed()
{
$feed = $this->createFeed();
$this->setFeedIsPublic($feed, true);
$entry = $this->createEntry($feed);
$this->addItem($entry, self::$DI['record_1']);
self::$DI['app']['authentication']->closeAccount();
self::$DI['client']->request('GET', '/permalink/v1/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->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

@@ -24,11 +24,12 @@ class FeedTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
$feed = $this->insertOneFeed(self::$DI['user']);
$crawler = self::$DI['client']->request('POST', '/prod/feeds/requestavailable/');
// print(self::$DI['client']->getResponse());exit;
$this->assertTrue(self::$DI['client']->getResponse()->isOk());
$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 +47,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;
@@ -1141,6 +1144,70 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase
->disableOriginalConstructor()
->getMock();
}
protected function createFeed()
{
$feed = new Feed();
$feed->setTitle('Feed')
->setSubtitle('subtitle');
self::$DI['app']['EM']->persist($feed);
self::$DI['app']['EM']->flush();
return $feed;
}
protected function createEntry(Feed $feed)
{
$publisher = new FeedPublisher();
$publisher->setFeed($feed)
->setIsOwner(true)
->setUsrId(self::$DI['user']->get_id());
$feed->addPublisher($publisher);
$entry = new FeedEntry();
$entry->setTitle('entry')
->setSubtitle('subtitle')
->setAuthorEmail('email')
->setAuthorName('name')
->setFeed($feed)
->setPublisher($publisher);
$feed->addEntry($entry);
self::$DI['app']['EM']->persist($publisher);
self::$DI['app']['EM']->persist($feed);
self::$DI['app']['EM']->persist($entry);
self::$DI['app']['EM']->flush();
return $entry;
}
protected function addItem(FeedEntry $entry, \record_adapter $record)
{
$item = new FeedItem();
$item->setEntry($entry)
->setRecordId($record->get_record_id())
->setSbasId($record->get_sbas_id());
$entry->addItem($item);
self::$DI['app']['EM']->persist($item);
self::$DI['app']['EM']->persist($entry);
self::$DI['app']['EM']->flush();
return $item;
}
protected function setFeedIsPublic(Feed $feed, $public)
{
$feed->setIsPublic($public);
self::$DI['app']['EM']->persist($feed);
self::$DI['app']['EM']->flush();
return $feed;
}
}
class CsrfTestProvider implements CsrfProviderInterface

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;