aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorNicolas LÅ“uillet <nicolas@loeuillet.org>2015-04-01 21:08:56 +0200
committerNicolas LÅ“uillet <nicolas@loeuillet.org>2015-04-01 21:08:56 +0200
commit1a93ee423b072ec3bcb0c437cbf9b488bdea245c (patch)
tree1466dcb1b465f9ead71c4dcbefe380853c5d846b /src
parentf98a2a0fc3ae8a5955bb811f083c3d2535f96791 (diff)
parent7d74a2f32b55fa9c33f5ecff57785e8d9e4de8ae (diff)
downloadwallabag-1a93ee423b072ec3bcb0c437cbf9b488bdea245c.tar.gz
wallabag-1a93ee423b072ec3bcb0c437cbf9b488bdea245c.tar.zst
wallabag-1a93ee423b072ec3bcb0c437cbf9b488bdea245c.zip
Merge pull request #1166 from wallabag/v2-rss
Add RSS feeds
Diffstat (limited to 'src')
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php1
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php58
-rw-r--r--src/Wallabag/CoreBundle/Controller/RssController.php84
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php20
-rw-r--r--src/Wallabag/CoreBundle/Entity/Config.php84
-rw-r--r--src/Wallabag/CoreBundle/Entity/User.php2
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/ConfigType.php2
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/RssType.php29
-rw-r--r--src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php92
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php8
-rw-r--r--src/Wallabag/CoreBundle/Repository/UserRepository.php26
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml7
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/Config/index.html.twig140
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/Entry/entries.xml.twig34
-rwxr-xr-xsrc/Wallabag/CoreBundle/Resources/views/themes/baggy/public/css/main.css24
-rw-r--r--src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php15
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php126
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php4
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php126
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/WallabagRestControllerTest.php2
-rw-r--r--src/Wallabag/CoreBundle/Tests/Mock/InstallCommandMock.php22
-rw-r--r--src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php220
-rw-r--r--src/Wallabag/CoreBundle/Tools/Utils.php27
23 files changed, 1068 insertions, 85 deletions
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index ac7583ea..bba2607d 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -198,6 +198,7 @@ class InstallCommand extends ContainerAwareCommand
198 $config = new Config($user); 198 $config = new Config($user);
199 $config->setTheme($this->getContainer()->getParameter('theme')); 199 $config->setTheme($this->getContainer()->getParameter('theme'));
200 $config->setItemsPerPage($this->getContainer()->getParameter('items_on_page')); 200 $config->setItemsPerPage($this->getContainer()->getParameter('items_on_page'));
201 $config->setRssLimit($this->getContainer()->getParameter('rss_limit'));
201 $config->setLanguage($this->getContainer()->getParameter('language')); 202 $config->setLanguage($this->getContainer()->getParameter('language'));
202 203
203 $em->persist($config); 204 $em->persist($config);
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index 4e895875..dbae3ea7 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -5,11 +5,14 @@ namespace Wallabag\CoreBundle\Controller;
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\JsonResponse;
8use Wallabag\CoreBundle\Entity\Config; 9use Wallabag\CoreBundle\Entity\Config;
9use Wallabag\CoreBundle\Entity\User; 10use Wallabag\CoreBundle\Entity\User;
10use Wallabag\CoreBundle\Form\Type\ChangePasswordType; 11use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
11use Wallabag\CoreBundle\Form\Type\UserType; 12use Wallabag\CoreBundle\Form\Type\UserType;
12use Wallabag\CoreBundle\Form\Type\NewUserType; 13use Wallabag\CoreBundle\Form\Type\NewUserType;
14use Wallabag\CoreBundle\Form\Type\RssType;
15use Wallabag\CoreBundle\Tools\Utils;
13 16
14class ConfigController extends Controller 17class ConfigController extends Controller
15{ 18{
@@ -77,6 +80,22 @@ class ConfigController extends Controller
77 return $this->redirect($this->generateUrl('config')); 80 return $this->redirect($this->generateUrl('config'));
78 } 81 }
79 82
83 // handle rss information
84 $rssForm = $this->createForm(new RssType(), $config);
85 $rssForm->handleRequest($request);
86
87 if ($rssForm->isValid()) {
88 $em->persist($config);
89 $em->flush();
90
91 $this->get('session')->getFlashBag()->add(
92 'notice',
93 'RSS information updated'
94 );
95
96 return $this->redirect($this->generateUrl('config'));
97 }
98
80 // handle adding new user 99 // handle adding new user
81 $newUser = new User(); 100 $newUser = new User();
82 $newUserForm = $this->createForm(new NewUserType(), $newUser); 101 $newUserForm = $this->createForm(new NewUserType(), $newUser);
@@ -88,6 +107,7 @@ class ConfigController extends Controller
88 $config = new Config($newUser); 107 $config = new Config($newUser);
89 $config->setTheme($this->container->getParameter('theme')); 108 $config->setTheme($this->container->getParameter('theme'));
90 $config->setItemsPerPage($this->container->getParameter('items_on_page')); 109 $config->setItemsPerPage($this->container->getParameter('items_on_page'));
110 $config->setRssLimit($this->container->getParameter('rss_limit'));
91 $config->setLanguage($this->container->getParameter('language')); 111 $config->setLanguage($this->container->getParameter('language'));
92 112
93 $em->persist($config); 113 $em->persist($config);
@@ -103,14 +123,44 @@ class ConfigController extends Controller
103 } 123 }
104 124
105 return $this->render('WallabagCoreBundle:Config:index.html.twig', array( 125 return $this->render('WallabagCoreBundle:Config:index.html.twig', array(
106 'configForm' => $configForm->createView(), 126 'form' => array(
107 'pwdForm' => $pwdForm->createView(), 127 'config' => $configForm->createView(),
108 'userForm' => $userForm->createView(), 128 'rss' => $rssForm->createView(),
109 'newUserForm' => $newUserForm->createView(), 129 'pwd' => $pwdForm->createView(),
130 'user' => $userForm->createView(),
131 'new_user' => $newUserForm->createView(),
132 ),
133 'rss' => array(
134 'username' => $user->getUsername(),
135 'token' => $config->getRssToken(),
136 )
110 )); 137 ));
111 } 138 }
112 139
113 /** 140 /**
141 * @param Request $request
142 *
143 * @Route("/generate-token", name="generate_token")
144 *
145 * @return JsonResponse
146 */
147 public function generateTokenAction(Request $request)
148 {
149 $config = $this->getConfig();
150 $config->setRssToken(Utils::generateToken());
151
152 $em = $this->getDoctrine()->getManager();
153 $em->persist($config);
154 $em->flush();
155
156 if ($request->isXmlHttpRequest()) {
157 return new JsonResponse(array('token' => $config->getRssToken()));
158 }
159
160 return $request->headers->get('referer') ? $this->redirect($request->headers->get('referer')) : $this->redirectToRoute('config');
161 }
162
163 /**
114 * Retrieve config for the current user. 164 * Retrieve config for the current user.
115 * If no config were found, create a new one. 165 * If no config were found, create a new one.
116 * 166 *
diff --git a/src/Wallabag/CoreBundle/Controller/RssController.php b/src/Wallabag/CoreBundle/Controller/RssController.php
new file mode 100644
index 00000000..14f1dcb2
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Controller/RssController.php
@@ -0,0 +1,84 @@
1<?php
2
3namespace Wallabag\CoreBundle\Controller;
4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
7use Symfony\Bundle\FrameworkBundle\Controller\Controller;
8use Wallabag\CoreBundle\Entity\User;
9use Wallabag\CoreBundle\Entity\Entry;
10
11class RssController extends Controller
12{
13 /**
14 * Shows unread entries for current user
15 *
16 * @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"})
17 * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
18 *
19 * @return \Symfony\Component\HttpFoundation\Response
20 */
21 public function showUnreadAction(User $user)
22 {
23 $entries = $this->getDoctrine()
24 ->getRepository('WallabagCoreBundle:Entry')
25 ->findUnreadByUser(
26 $user->getId(),
27 0,
28 $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit')
29 );
30
31 return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
32 'type' => 'unread',
33 'entries' => $entries,
34 ));
35 }
36
37 /**
38 * Shows read entries for current user
39 *
40 * @Route("/{username}/{token}/archive.xml", name="archive_rss")
41 * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
42 *
43 * @return \Symfony\Component\HttpFoundation\Response
44 */
45 public function showArchiveAction(User $user)
46 {
47 $entries = $this->getDoctrine()
48 ->getRepository('WallabagCoreBundle:Entry')
49 ->findArchiveByUser(
50 $user->getId(),
51 0,
52 $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit')
53 );
54
55 return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
56 'type' => 'archive',
57 'entries' => $entries,
58 ));
59 }
60
61 /**
62 * Shows starred entries for current user
63 *
64 * @Route("/{username}/{token}/starred.xml", name="starred_rss")
65 * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
66 *
67 * @return \Symfony\Component\HttpFoundation\Response
68 */
69 public function showStarredAction(User $user)
70 {
71 $entries = $this->getDoctrine()
72 ->getRepository('WallabagCoreBundle:Entry')
73 ->findStarredByUser(
74 $user->getId(),
75 0,
76 $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit')
77 );
78
79 return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
80 'type' => 'starred',
81 'entries' => $entries,
82 ));
83 }
84}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
index ce12ec5d..54d0d6b6 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
@@ -67,6 +67,26 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
67 67
68 $this->addReference('entry4', $entry4); 68 $this->addReference('entry4', $entry4);
69 69
70 $entry5 = new Entry($this->getReference('admin-user'));
71 $entry5->setUrl('http://0.0.0.0');
72 $entry5->setTitle('test title entry5');
73 $entry5->setContent('This is my content /o/');
74 $entry5->setStarred(true);
75
76 $manager->persist($entry5);
77
78 $this->addReference('entry5', $entry5);
79
80 $entry6 = new Entry($this->getReference('admin-user'));
81 $entry6->setUrl('http://0.0.0.0');
82 $entry6->setTitle('test title entry6');
83 $entry6->setContent('This is my content /o/');
84 $entry6->setArchived(true);
85
86 $manager->persist($entry6);
87
88 $this->addReference('entry6', $entry6);
89
70 $manager->flush(); 90 $manager->flush();
71 } 91 }
72 92
diff --git a/src/Wallabag/CoreBundle/Entity/Config.php b/src/Wallabag/CoreBundle/Entity/Config.php
index 91f9bfe8..9f079656 100644
--- a/src/Wallabag/CoreBundle/Entity/Config.php
+++ b/src/Wallabag/CoreBundle/Entity/Config.php
@@ -32,12 +32,17 @@ class Config
32 private $theme; 32 private $theme;
33 33
34 /** 34 /**
35 * @var string 35 * @var integer
36 * 36 *
37 * @Assert\NotBlank() 37 * @Assert\NotBlank()
38 * @Assert\Range(
39 * min = 1,
40 * max = 100000,
41 * maxMessage = "This will certainly kill the app"
42 * )
38 * @ORM\Column(name="items_per_page", type="integer", nullable=false) 43 * @ORM\Column(name="items_per_page", type="integer", nullable=false)
39 */ 44 */
40 private $items_per_page; 45 private $itemsPerPage;
41 46
42 /** 47 /**
43 * @var string 48 * @var string
@@ -48,6 +53,25 @@ class Config
48 private $language; 53 private $language;
49 54
50 /** 55 /**
56 * @var string
57 *
58 * @ORM\Column(name="rss_token", type="string", nullable=true)
59 */
60 private $rssToken;
61
62 /**
63 * @var integer
64 *
65 * @ORM\Column(name="rss_limit", type="integer", nullable=true)
66 * @Assert\Range(
67 * min = 1,
68 * max = 100000,
69 * maxMessage = "This will certainly kill the app"
70 * )
71 */
72 private $rssLimit;
73
74 /**
51 * @ORM\OneToOne(targetEntity="User", inversedBy="config") 75 * @ORM\OneToOne(targetEntity="User", inversedBy="config")
52 */ 76 */
53 private $user; 77 private $user;
@@ -58,8 +82,6 @@ class Config
58 public function __construct(User $user) 82 public function __construct(User $user)
59 { 83 {
60 $this->user = $user; 84 $this->user = $user;
61 $this->items_per_page = 12;
62 $this->language = 'en_US';
63 } 85 }
64 86
65 /** 87 /**
@@ -96,26 +118,26 @@ class Config
96 } 118 }
97 119
98 /** 120 /**
99 * Set items_per_page 121 * Set itemsPerPage
100 * 122 *
101 * @param integer $itemsPerPage 123 * @param integer $itemsPerPage
102 * @return Config 124 * @return Config
103 */ 125 */
104 public function setItemsPerPage($itemsPerPage) 126 public function setItemsPerPage($itemsPerPage)
105 { 127 {
106 $this->items_per_page = $itemsPerPage; 128 $this->itemsPerPage = $itemsPerPage;
107 129
108 return $this; 130 return $this;
109 } 131 }
110 132
111 /** 133 /**
112 * Get items_per_page 134 * Get itemsPerPage
113 * 135 *
114 * @return integer 136 * @return integer
115 */ 137 */
116 public function getItemsPerPage() 138 public function getItemsPerPage()
117 { 139 {
118 return $this->items_per_page; 140 return $this->itemsPerPage;
119 } 141 }
120 142
121 /** 143 /**
@@ -163,4 +185,50 @@ class Config
163 { 185 {
164 return $this->user; 186 return $this->user;
165 } 187 }
188
189 /**
190 * Set rssToken
191 *
192 * @param string $rssToken
193 * @return Config
194 */
195 public function setRssToken($rssToken)
196 {
197 $this->rssToken = $rssToken;
198
199 return $this;
200 }
201
202 /**
203 * Get rssToken
204 *
205 * @return string
206 */
207 public function getRssToken()
208 {
209 return $this->rssToken;
210 }
211
212 /**
213 * Set rssLimit
214 *
215 * @param string $rssLimit
216 * @return Config
217 */
218 public function setRssLimit($rssLimit)
219 {
220 $this->rssLimit = $rssLimit;
221
222 return $this;
223 }
224
225 /**
226 * Get rssLimit
227 *
228 * @return string
229 */
230 public function getRssLimit()
231 {
232 return $this->rssLimit;
233 }
166} 234}
diff --git a/src/Wallabag/CoreBundle/Entity/User.php b/src/Wallabag/CoreBundle/Entity/User.php
index 6a7619ac..e75e3a83 100644
--- a/src/Wallabag/CoreBundle/Entity/User.php
+++ b/src/Wallabag/CoreBundle/Entity/User.php
@@ -14,7 +14,7 @@ use JMS\Serializer\Annotation\Expose;
14 * User 14 * User
15 * 15 *
16 * @ORM\Table(name="user") 16 * @ORM\Table(name="user")
17 * @ORM\Entity 17 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\UserRepository")
18 * @ORM\HasLifecycleCallbacks() 18 * @ORM\HasLifecycleCallbacks()
19 * @ExclusionPolicy("all") 19 * @ExclusionPolicy("all")
20 */ 20 */
diff --git a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
index 0c8706e2..0fcf020a 100644
--- a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
@@ -24,7 +24,7 @@ class ConfigType extends AbstractType
24 { 24 {
25 $builder 25 $builder
26 ->add('theme', 'choice', array('choices' => $this->themes)) 26 ->add('theme', 'choice', array('choices' => $this->themes))
27 ->add('items_per_page', 'text') 27 ->add('items_per_page')
28 ->add('language') 28 ->add('language')
29 ->add('save', 'submit') 29 ->add('save', 'submit')
30 ; 30 ;
diff --git a/src/Wallabag/CoreBundle/Form/Type/RssType.php b/src/Wallabag/CoreBundle/Form/Type/RssType.php
new file mode 100644
index 00000000..a1ab990f
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Form/Type/RssType.php
@@ -0,0 +1,29 @@
1<?php
2namespace Wallabag\CoreBundle\Form\Type;
3
4use Symfony\Component\Form\AbstractType;
5use Symfony\Component\Form\FormBuilderInterface;
6use Symfony\Component\OptionsResolver\OptionsResolverInterface;
7
8class RssType extends AbstractType
9{
10 public function buildForm(FormBuilderInterface $builder, array $options)
11 {
12 $builder
13 ->add('rss_limit')
14 ->add('save', 'submit')
15 ;
16 }
17
18 public function setDefaultOptions(OptionsResolverInterface $resolver)
19 {
20 $resolver->setDefaults(array(
21 'data_class' => 'Wallabag\CoreBundle\Entity\Config',
22 ));
23 }
24
25 public function getName()
26 {
27 return 'rss_config';
28 }
29}
diff --git a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php b/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
new file mode 100644
index 00000000..ea2bda17
--- /dev/null
+++ b/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
@@ -0,0 +1,92 @@
1<?php
2
3namespace Wallabag\CoreBundle\ParamConverter;
4
5use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
6use Doctrine\Common\Persistence\ManagerRegistry;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
10use Wallabag\CoreBundle\Entity\User;
11
12/**
13 * ParamConverter used in the RSS controller to retrieve the right user according to
14 * username & token given in the url.
15 *
16 * @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter
17 */
18class UsernameRssTokenConverter implements ParamConverterInterface
19{
20 private $registry;
21
22 /**
23 * @param ManagerRegistry $registry Manager registry
24 */
25 public function __construct(ManagerRegistry $registry = null)
26 {
27 $this->registry = $registry;
28 }
29
30 /**
31 * {@inheritdoc}
32 *
33 * Check, if object supported by our converter
34 */
35 public function supports(ParamConverter $configuration)
36 {
37 // If there is no manager, this means that only Doctrine DBAL is configured
38 // In this case we can do nothing and just return
39 if (null === $this->registry || !count($this->registry->getManagers())) {
40 return false;
41 }
42
43 // Check, if option class was set in configuration
44 if (null === $configuration->getClass()) {
45 return false;
46 }
47
48 // Get actual entity manager for class
49 $em = $this->registry->getManagerForClass($configuration->getClass());
50
51 // Check, if class name is what we need
52 if ('Wallabag\CoreBundle\Entity\User' !== $em->getClassMetadata($configuration->getClass())->getName()) {
53 return false;
54 }
55
56 return true;
57 }
58
59 /**
60 * {@inheritdoc}
61 *
62 * Applies converting
63 *
64 * @throws \InvalidArgumentException When route attributes are missing
65 * @throws NotFoundHttpException When object not found
66 */
67 public function apply(Request $request, ParamConverter $configuration)
68 {
69 $username = $request->attributes->get('username');
70 $rssToken = $request->attributes->get('token');
71
72 // Check, if route attributes exists
73 if (null === $username || null === $rssToken) {
74 throw new \InvalidArgumentException('Route attribute is missing');
75 }
76
77 // Get actual entity manager for class
78 $em = $this->registry->getManagerForClass($configuration->getClass());
79
80 $userRepository = $em->getRepository($configuration->getClass());
81
82 // Try to find user by its username and config rss_token
83 $user = $userRepository->findOneByUsernameAndRsstoken($username, $rssToken);
84
85 if (null === $user || !($user instanceof User)) {
86 throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass()));
87 }
88
89 // Map found user to the route's parameter
90 $request->attributes->set($configuration->getName(), $user);
91 }
92}
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index 53e8e2ba..a8c138a9 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -26,7 +26,7 @@ class EntryRepository extends EntityRepository
26 ->leftJoin('e.user', 'u') 26 ->leftJoin('e.user', 'u')
27 ->where('e.isArchived = false') 27 ->where('e.isArchived = false')
28 ->andWhere('u.id =:userId')->setParameter('userId', $userId) 28 ->andWhere('u.id =:userId')->setParameter('userId', $userId)
29 ->orderBy('e.createdAt', 'desc') 29 ->orderBy('e.id', 'desc')
30 ->getQuery(); 30 ->getQuery();
31 31
32 $paginator = new Paginator($qb); 32 $paginator = new Paginator($qb);
@@ -52,7 +52,7 @@ class EntryRepository extends EntityRepository
52 ->leftJoin('e.user', 'u') 52 ->leftJoin('e.user', 'u')
53 ->where('e.isArchived = true') 53 ->where('e.isArchived = true')
54 ->andWhere('u.id =:userId')->setParameter('userId', $userId) 54 ->andWhere('u.id =:userId')->setParameter('userId', $userId)
55 ->orderBy('e.createdAt', 'desc') 55 ->orderBy('e.id', 'desc')
56 ->getQuery(); 56 ->getQuery();
57 57
58 $paginator = new Paginator($qb); 58 $paginator = new Paginator($qb);
@@ -78,7 +78,7 @@ class EntryRepository extends EntityRepository
78 ->leftJoin('e.user', 'u') 78 ->leftJoin('e.user', 'u')
79 ->where('e.isStarred = true') 79 ->where('e.isStarred = true')
80 ->andWhere('u.id =:userId')->setParameter('userId', $userId) 80 ->andWhere('u.id =:userId')->setParameter('userId', $userId)
81 ->orderBy('e.createdAt', 'desc') 81 ->orderBy('e.id', 'desc')
82 ->getQuery(); 82 ->getQuery();
83 83
84 $paginator = new Paginator($qb); 84 $paginator = new Paginator($qb);
@@ -111,7 +111,7 @@ class EntryRepository extends EntityRepository
111 } 111 }
112 112
113 if ('created' === $sort) { 113 if ('created' === $sort) {
114 $qb->orderBy('e.createdAt', $order); 114 $qb->orderBy('e.id', $order);
115 } elseif ('updated' === $sort) { 115 } elseif ('updated' === $sort) {
116 $qb->orderBy('e.updatedAt', $order); 116 $qb->orderBy('e.updatedAt', $order);
117 } 117 }
diff --git a/src/Wallabag/CoreBundle/Repository/UserRepository.php b/src/Wallabag/CoreBundle/Repository/UserRepository.php
new file mode 100644
index 00000000..aab3dedc
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Repository/UserRepository.php
@@ -0,0 +1,26 @@
1<?php
2
3namespace Wallabag\CoreBundle\Repository;
4
5use Doctrine\ORM\EntityRepository;
6
7class UserRepository extends EntityRepository
8{
9 /**
10 * Find a user by its username and rss roken
11 *
12 * @param string $username
13 * @param string $rssToken
14 *
15 * @return User|null
16 */
17 public function findOneByUsernameAndRsstoken($username, $rssToken)
18 {
19 return $this->createQueryBuilder('u')
20 ->leftJoin('u.config', 'c')
21 ->where('c.rssToken = :rss_token')->setParameter('rss_token', $rssToken)
22 ->andWhere('u.username = :username')->setParameter('username', $username)
23 ->getQuery()
24 ->getOneOrNullResult();
25 }
26}
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 062e1651..0f4db94e 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -36,3 +36,10 @@ services:
36 - @doctrine 36 - @doctrine
37 tags: 37 tags:
38 - { name: form.type, alias: forgot_password } 38 - { name: form.type, alias: forgot_password }
39
40 wallabag_core.param_converter.username_rsstoken_converter:
41 class: Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter
42 tags:
43 - { name: request.param_converter, converter: username_rsstoken_converter }
44 arguments:
45 - @doctrine
diff --git a/src/Wallabag/CoreBundle/Resources/views/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/Config/index.html.twig
index 1835d26e..f2a98dfb 100644
--- a/src/Wallabag/CoreBundle/Resources/views/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/Config/index.html.twig
@@ -5,129 +5,173 @@
5{% block content %} 5{% block content %}
6 <h2>{% trans %}Wallabag configuration{% endtrans %}</h2> 6 <h2>{% trans %}Wallabag configuration{% endtrans %}</h2>
7 7
8 <form action="{{ path('config') }}" method="post" {{ form_enctype(configForm) }}> 8 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.config) }}>
9 {{ form_errors(configForm) }} 9 {{ form_errors(form.config) }}
10 10
11 <fieldset class="w500p inline"> 11 <fieldset class="w500p inline">
12 <div class="row"> 12 <div class="row">
13 {{ form_label(configForm.theme) }} 13 {{ form_label(form.config.theme) }}
14 {{ form_errors(configForm.theme) }} 14 {{ form_errors(form.config.theme) }}
15 {{ form_widget(configForm.theme) }} 15 {{ form_widget(form.config.theme) }}
16 </div> 16 </div>
17 </fieldset> 17 </fieldset>
18 18
19 <fieldset class="w500p inline"> 19 <fieldset class="w500p inline">
20 <div class="row"> 20 <div class="row">
21 {{ form_label(configForm.items_per_page) }} 21 {{ form_label(form.config.items_per_page) }}
22 {{ form_errors(configForm.items_per_page) }} 22 {{ form_errors(form.config.items_per_page) }}
23 {{ form_widget(configForm.items_per_page) }} 23 {{ form_widget(form.config.items_per_page) }}
24 </div> 24 </div>
25 </fieldset> 25 </fieldset>
26 26
27 <fieldset class="w500p inline"> 27 <fieldset class="w500p inline">
28 <div class="row"> 28 <div class="row">
29 {{ form_label(configForm.language) }} 29 {{ form_label(form.config.language) }}
30 {{ form_errors(configForm.language) }} 30 {{ form_errors(form.config.language) }}
31 {{ form_widget(configForm.language) }} 31 {{ form_widget(form.config.language) }}
32 </div> 32 </div>
33 </fieldset> 33 </fieldset>
34 34
35 {{ form_rest(configForm) }} 35 {{ form_rest(form.config) }}
36 </form>
37
38 <h2>{% trans %}RSS configuration{% endtrans %}</h2>
39
40 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.rss) }}>
41 {{ form_errors(form.rss) }}
42
43 <fieldset class="w500p inline">
44 <div class="row">
45 <label>Rss token</label>
46 {% if rss.token %}
47 {{ rss.token }}
48 {% else %}
49 <em>No token</em>
50 {% endif %}
51 –
52 <a href="{{ path('generate_token') }}">Regenerate ?</a>
53 </div>
54 </fieldset>
55
56 <fieldset class="w500p inline">
57 <div class="row">
58 <label>Rss links:</label>
59 {% if rss.token %}
60 <ul>
61 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">unread</a></li>
62 <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">fav</a></li>
63 <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">archives</a></li>
64 </ul>
65 {% else %}
66 <strong>You need to generate a token first.</strong>
67 {% endif %}
68 </div>
69 </fieldset>
70
71 <fieldset class="w500p inline">
72 <div class="row">
73 {{ form_label(form.rss.rss_limit) }}
74 {{ form_errors(form.rss.rss_limit) }}
75 {{ form_widget(form.rss.rss_limit) }}
76 </div>
77 </fieldset>
78
79 {{ form_rest(form.rss) }}
36 </form> 80 </form>
37 81
38 <h2>{% trans %}User information{% endtrans %}</h2> 82 <h2>{% trans %}User information{% endtrans %}</h2>
39 83
40 <form action="{{ path('config') }}" method="post" {{ form_enctype(userForm) }}> 84 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.user) }}>
41 {{ form_errors(userForm) }} 85 {{ form_errors(form.user) }}
42 86
43 <fieldset class="w500p inline"> 87 <fieldset class="w500p inline">
44 <div class="row"> 88 <div class="row">
45 {{ form_label(userForm.username) }} 89 {{ form_label(form.user.username) }}
46 {{ form_errors(userForm.username) }} 90 {{ form_errors(form.user.username) }}
47 {{ form_widget(userForm.username) }} 91 {{ form_widget(form.user.username) }}
48 </div> 92 </div>
49 </fieldset> 93 </fieldset>
50 94
51 <fieldset class="w500p inline"> 95 <fieldset class="w500p inline">
52 <div class="row"> 96 <div class="row">
53 {{ form_label(userForm.name) }} 97 {{ form_label(form.user.name) }}
54 {{ form_errors(userForm.name) }} 98 {{ form_errors(form.user.name) }}
55 {{ form_widget(userForm.name) }} 99 {{ form_widget(form.user.name) }}
56 </div> 100 </div>
57 </fieldset> 101 </fieldset>
58 102
59 <fieldset class="w500p inline"> 103 <fieldset class="w500p inline">
60 <div class="row"> 104 <div class="row">
61 {{ form_label(userForm.email) }} 105 {{ form_label(form.user.email) }}
62 {{ form_errors(userForm.email) }} 106 {{ form_errors(form.user.email) }}
63 {{ form_widget(userForm.email) }} 107 {{ form_widget(form.user.email) }}
64 </div> 108 </div>
65 </fieldset> 109 </fieldset>
66 110
67 {{ form_rest(userForm) }} 111 {{ form_rest(form.user) }}
68 </form> 112 </form>
69 113
70 <h2>{% trans %}Change your password{% endtrans %}</h2> 114 <h2>{% trans %}Change your password{% endtrans %}</h2>
71 115
72 <form action="{{ path('config') }}" method="post" {{ form_enctype(pwdForm) }}> 116 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.pwd) }}>
73 {{ form_errors(pwdForm) }} 117 {{ form_errors(form.pwd) }}
74 118
75 <fieldset class="w500p inline"> 119 <fieldset class="w500p inline">
76 <div class="row"> 120 <div class="row">
77 {{ form_label(pwdForm.old_password) }} 121 {{ form_label(form.pwd.old_password) }}
78 {{ form_errors(pwdForm.old_password) }} 122 {{ form_errors(form.pwd.old_password) }}
79 {{ form_widget(pwdForm.old_password) }} 123 {{ form_widget(form.pwd.old_password) }}
80 </div> 124 </div>
81 </fieldset> 125 </fieldset>
82 126
83 <fieldset class="w500p inline"> 127 <fieldset class="w500p inline">
84 <div class="row"> 128 <div class="row">
85 {{ form_label(pwdForm.new_password.first) }} 129 {{ form_label(form.pwd.new_password.first) }}
86 {{ form_errors(pwdForm.new_password.first) }} 130 {{ form_errors(form.pwd.new_password.first) }}
87 {{ form_widget(pwdForm.new_password.first) }} 131 {{ form_widget(form.pwd.new_password.first) }}
88 </div> 132 </div>
89 </fieldset> 133 </fieldset>
90 134
91 <fieldset class="w500p inline"> 135 <fieldset class="w500p inline">
92 <div class="row"> 136 <div class="row">
93 {{ form_label(pwdForm.new_password.second) }} 137 {{ form_label(form.pwd.new_password.second) }}
94 {{ form_errors(pwdForm.new_password.second) }} 138 {{ form_errors(form.pwd.new_password.second) }}
95 {{ form_widget(pwdForm.new_password.second) }} 139 {{ form_widget(form.pwd.new_password.second) }}
96 </div> 140 </div>
97 </fieldset> 141 </fieldset>
98 142
99 {{ form_rest(pwdForm) }} 143 {{ form_rest(form.pwd) }}
100 </form> 144 </form>
101 145
102 <h2>{% trans %}Add a user{% endtrans %}</h2> 146 <h2>{% trans %}Add a user{% endtrans %}</h2>
103 147
104 <form action="{{ path('config') }}" method="post" {{ form_enctype(newUserForm) }}> 148 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_user) }}>
105 {{ form_errors(newUserForm) }} 149 {{ form_errors(form.new_user) }}
106 150
107 <fieldset class="w500p inline"> 151 <fieldset class="w500p inline">
108 <div class="row"> 152 <div class="row">
109 {{ form_label(newUserForm.username) }} 153 {{ form_label(form.new_user.username) }}
110 {{ form_errors(newUserForm.username) }} 154 {{ form_errors(form.new_user.username) }}
111 {{ form_widget(newUserForm.username) }} 155 {{ form_widget(form.new_user.username) }}
112 </div> 156 </div>
113 </fieldset> 157 </fieldset>
114 158
115 <fieldset class="w500p inline"> 159 <fieldset class="w500p inline">
116 <div class="row"> 160 <div class="row">
117 {{ form_label(newUserForm.password) }} 161 {{ form_label(form.new_user.password) }}
118 {{ form_errors(newUserForm.password) }} 162 {{ form_errors(form.new_user.password) }}
119 {{ form_widget(newUserForm.password) }} 163 {{ form_widget(form.new_user.password) }}
120 </div> 164 </div>
121 </fieldset> 165 </fieldset>
122 166
123 <fieldset class="w500p inline"> 167 <fieldset class="w500p inline">
124 <div class="row"> 168 <div class="row">
125 {{ form_label(newUserForm.email) }} 169 {{ form_label(form.new_user.email) }}
126 {{ form_errors(newUserForm.email) }} 170 {{ form_errors(form.new_user.email) }}
127 {{ form_widget(newUserForm.email) }} 171 {{ form_widget(form.new_user.email) }}
128 </div> 172 </div>
129 </fieldset> 173 </fieldset>
130 174
131 {{ form_rest(newUserForm) }} 175 {{ form_rest(form.new_user) }}
132 </form> 176 </form>
133{% endblock %} 177{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/Entry/entries.xml.twig b/src/Wallabag/CoreBundle/Resources/views/Entry/entries.xml.twig
new file mode 100644
index 00000000..5ec9bc03
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/Entry/entries.xml.twig
@@ -0,0 +1,34 @@
1<?xml version="1.0" encoding="utf-8"?>
2<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
3 <channel>
4 <title>wallabag — {{type}} feed</title>
5 <link>{{ url('unread') }}</link>
6 <pubDate>{{ "now"|date('D, d M Y H:i:s') }}</pubDate>
7 <generator>wallabag</generator>
8 <description>wallabag {{type}} elements</description>
9
10 {% for entry in entries %}
11
12 <item>
13 <title><![CDATA[{{ entry.title }}]]></title>
14 <source url="{{ url('view', { 'id': entry.id }) }}">wallabag</source>
15 <link>{{ url('view', { 'id': entry.id }) }}</link>
16 <guid>{{ url('view', { 'id': entry.id }) }}</guid>
17 <pubDate>{{ entry.createdAt|date('D, d M Y H:i:s') }}</pubDate>
18 <description>
19 <![CDATA[
20 {%- if entry.content|readingTime > 0 -%}
21 {% trans %}estimated reading time :{% endtrans %} {{ entry.content|readingTime }} min
22 {%- else -%}
23 {% trans %}estimated reading time :{% endtrans %} &lt; 1 min
24 {%- endif %}
25
26 {{ entry.content|raw -}}
27 ]]>
28 </description>
29 </item>
30
31 {% endfor %}
32
33 </channel>
34</rss>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/public/css/main.css b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/public/css/main.css
index 1df82910..ff1a36a1 100755
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/public/css/main.css
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/public/css/main.css
@@ -7,7 +7,7 @@
7 4 = Messages 7 4 = Messages
8 5 = Article 8 5 = Article
9 6 = Media queries 9 6 = Media queries
10 10
11 ========================================================================== */ 11 ========================================================================== */
12 12
13html { 13html {
@@ -42,17 +42,17 @@ body {
42 position: absolute; 42 position: absolute;
43 top: 2em; 43 top: 2em;
44 left: 50%; 44 left: 50%;
45 margin-left: -55px; 45 margin-left: -55px;
46} 46}
47 47
48/* ========================================================================== 48/* ==========================================================================
49 1 = Style Guide 49 1 = Style Guide
50 ========================================================================== */ 50 ========================================================================== */
51 51
52::selection { 52::selection {
53 color: #FFF; 53 color: #FFF;
54 background-color: #000; 54 background-color: #000;
55} 55}
56 56
57.desktopHide { 57.desktopHide {
58 display: none; 58 display: none;
@@ -62,7 +62,7 @@ body {
62 position: fixed; 62 position: fixed;
63 z-index: 20; 63 z-index: 20;
64 top: 0.4em; 64 top: 0.4em;
65 left: 0.6em; 65 left: 0.6em;
66} 66}
67 67
68h2, h3, h4 { 68h2, h3, h4 {
@@ -89,7 +89,7 @@ form fieldset {
89 margin: 0; 89 margin: 0;
90} 90}
91 91
92form input[type="text"], select, form input[type="password"], form input[type="url"], form input[type="email"] { 92form input[type="text"], form input[type="number"], select, form input[type="password"], form input[type="url"], form input[type="email"] {
93 border: 1px solid #999; 93 border: 1px solid #999;
94 padding: 0.5em 1em; 94 padding: 0.5em 1em;
95 min-width: 12em; 95 min-width: 12em;
@@ -149,7 +149,7 @@ form button, input[type="submit"] {
149 149
150#bookmarklet { 150#bookmarklet {
151 cursor: move; 151 cursor: move;
152} 152}
153 153
154h2:after { 154h2:after {
155 content: ""; 155 content: "";
@@ -296,7 +296,7 @@ h2:after {
296/* ========================================================================== 296/* ==========================================================================
297 2 = Layout 297 2 = Layout
298 ========================================================================== */ 298 ========================================================================== */
299 299
300#content { 300#content {
301 margin-top: 5em; 301 margin-top: 5em;
302 min-height: 30em; 302 min-height: 30em;
@@ -653,7 +653,7 @@ a.add-to-wallabag-link-after:after {
653/* ========================================================================== 653/* ==========================================================================
654 3 = Pictos 654 3 = Pictos
655 ========================================================================== */ 655 ========================================================================== */
656 656
657@font-face { 657@font-face {
658 font-family: 'icomoon'; 658 font-family: 'icomoon';
659 src:url('../fonts/icomoon.eot?-s0mcsx'); 659 src:url('../fonts/icomoon.eot?-s0mcsx');
@@ -866,7 +866,7 @@ blockquote {
866 color: #FFF; 866 color: #FFF;
867 text-decoration: none; 867 text-decoration: none;
868} 868}
869 869
870 #article_toolbar a:hover, #article_toolbar a:focus { 870 #article_toolbar a:hover, #article_toolbar a:focus {
871 background-color: #999; 871 background-color: #999;
872 } 872 }
@@ -1052,7 +1052,7 @@ pre code {
1052 #article_toolbar a { 1052 #article_toolbar a {
1053 padding: 0.3em 0.4em 0.2em; 1053 padding: 0.3em 0.4em 0.2em;
1054 } 1054 }
1055 1055
1056 #display-mode { 1056 #display-mode {
1057 display: none; 1057 display: none;
1058 } 1058 }
diff --git a/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php b/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php
index 64f6c329..f689b532 100644
--- a/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php
@@ -4,6 +4,7 @@ namespace Wallabag\CoreBundle\Tests\Command;
4 4
5use Wallabag\CoreBundle\Tests\WallabagTestCase; 5use Wallabag\CoreBundle\Tests\WallabagTestCase;
6use Wallabag\CoreBundle\Command\InstallCommand; 6use Wallabag\CoreBundle\Command\InstallCommand;
7use Wallabag\CoreBundle\Tests\Mock\InstallCommandMock;
7use Symfony\Bundle\FrameworkBundle\Console\Application; 8use Symfony\Bundle\FrameworkBundle\Console\Application;
8use Symfony\Component\Console\Tester\CommandTester; 9use Symfony\Component\Console\Tester\CommandTester;
9use Symfony\Component\Console\Input\ArrayInput; 10use Symfony\Component\Console\Input\ArrayInput;
@@ -30,7 +31,7 @@ class InstallCommandTest extends WallabagTestCase
30 $this->container = static::$kernel->getContainer(); 31 $this->container = static::$kernel->getContainer();
31 32
32 $application = new Application(static::$kernel); 33 $application = new Application(static::$kernel);
33 $application->add(new InstallCommand()); 34 $application->add(new InstallCommandMock());
34 35
35 $command = $application->find('wallabag:install'); 36 $command = $application->find('wallabag:install');
36 37
@@ -64,7 +65,7 @@ class InstallCommandTest extends WallabagTestCase
64 $this->container = static::$kernel->getContainer(); 65 $this->container = static::$kernel->getContainer();
65 66
66 $application = new Application(static::$kernel); 67 $application = new Application(static::$kernel);
67 $application->add(new InstallCommand()); 68 $application->add(new InstallCommandMock());
68 69
69 $command = $application->find('wallabag:install'); 70 $command = $application->find('wallabag:install');
70 71
@@ -97,6 +98,9 @@ class InstallCommandTest extends WallabagTestCase
97 $this->assertContains('Droping database, creating database and schema', $tester->getDisplay()); 98 $this->assertContains('Droping database, creating database and schema', $tester->getDisplay());
98 } 99 }
99 100
101 /**
102 * @group command-doctrine
103 */
100 public function testRunInstallCommandWithDatabaseRemoved() 104 public function testRunInstallCommandWithDatabaseRemoved()
101 { 105 {
102 $this->container = static::$kernel->getContainer(); 106 $this->container = static::$kernel->getContainer();
@@ -148,7 +152,7 @@ class InstallCommandTest extends WallabagTestCase
148 $this->container = static::$kernel->getContainer(); 152 $this->container = static::$kernel->getContainer();
149 153
150 $application = new Application(static::$kernel); 154 $application = new Application(static::$kernel);
151 $application->add(new InstallCommand()); 155 $application->add(new InstallCommandMock());
152 156
153 $command = $application->find('wallabag:install'); 157 $command = $application->find('wallabag:install');
154 158
@@ -181,6 +185,9 @@ class InstallCommandTest extends WallabagTestCase
181 $this->assertContains('Droping schema and creating schema', $tester->getDisplay()); 185 $this->assertContains('Droping schema and creating schema', $tester->getDisplay());
182 } 186 }
183 187
188 /**
189 * @group command-doctrine
190 */
184 public function testRunInstallCommandChooseNothing() 191 public function testRunInstallCommandChooseNothing()
185 { 192 {
186 $this->container = static::$kernel->getContainer(); 193 $this->container = static::$kernel->getContainer();
@@ -242,7 +249,7 @@ class InstallCommandTest extends WallabagTestCase
242 $this->container = static::$kernel->getContainer(); 249 $this->container = static::$kernel->getContainer();
243 250
244 $application = new Application(static::$kernel); 251 $application = new Application(static::$kernel);
245 $application->add(new InstallCommand()); 252 $application->add(new InstallCommandMock());
246 253
247 $command = $application->find('wallabag:install'); 254 $command = $application->find('wallabag:install');
248 255
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
index d7d341aa..11c86423 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
@@ -28,6 +28,8 @@ class ConfigControllerTest extends WallabagTestCase
28 $this->assertCount(1, $crawler->filter('button[id=config_save]')); 28 $this->assertCount(1, $crawler->filter('button[id=config_save]'));
29 $this->assertCount(1, $crawler->filter('button[id=change_passwd_save]')); 29 $this->assertCount(1, $crawler->filter('button[id=change_passwd_save]'));
30 $this->assertCount(1, $crawler->filter('button[id=user_save]')); 30 $this->assertCount(1, $crawler->filter('button[id=user_save]'));
31 $this->assertCount(1, $crawler->filter('button[id=new_user_save]'));
32 $this->assertCount(1, $crawler->filter('button[id=rss_config_save]'));
31 } 33 }
32 34
33 public function testUpdate() 35 public function testUpdate()
@@ -347,4 +349,128 @@ class ConfigControllerTest extends WallabagTestCase
347 $this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text'))); 349 $this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text')));
348 $this->assertContains('User "wallace" added', $alert[0]); 350 $this->assertContains('User "wallace" added', $alert[0]);
349 } 351 }
352
353 public function testRssUpdateResetToken()
354 {
355 $this->logInAs('admin');
356 $client = $this->getClient();
357
358 // reset the token
359 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
360 $user = $em
361 ->getRepository('WallabagCoreBundle:User')
362 ->findOneByUsername('admin');
363
364 if (!$user) {
365 $this->markTestSkipped('No user found in db.');
366 }
367
368 $config = $user->getConfig();
369 $config->setRssToken(null);
370 $em->persist($config);
371 $em->flush();
372
373 $crawler = $client->request('GET', '/config');
374
375 $this->assertEquals(200, $client->getResponse()->getStatusCode());
376
377 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(array('_text')));
378 $this->assertContains('You need to generate a token first.', $body[0]);
379
380 $client->request('GET', '/generate-token');
381 $this->assertEquals(302, $client->getResponse()->getStatusCode());
382
383 $crawler = $client->followRedirect();
384
385 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(array('_text')));
386 $this->assertNotContains('You need to generate a token first.', $body[0]);
387 }
388
389 public function testGenerateTokenAjax()
390 {
391 $this->logInAs('admin');
392 $client = $this->getClient();
393
394 $client->request(
395 'GET',
396 '/generate-token',
397 array(),
398 array(),
399 array('HTTP_X-Requested-With' => 'XMLHttpRequest')
400 );
401
402 $this->assertEquals(200, $client->getResponse()->getStatusCode());
403 $content = json_decode($client->getResponse()->getContent(), true);;
404 $this->assertArrayHasKey('token', $content);
405 }
406
407 public function testRssUpdate()
408 {
409 $this->logInAs('admin');
410 $client = $this->getClient();
411
412 $crawler = $client->request('GET', '/config');
413
414 if (500 == $client->getResponse()->getStatusCode()) {
415 var_export($client->getResponse()->getContent());
416 die();
417 }
418
419 $this->assertEquals(200, $client->getResponse()->getStatusCode());
420
421 $form = $crawler->filter('button[id=rss_config_save]')->form();
422
423 $data = array(
424 'rss_config[rss_limit]' => 12,
425 );
426
427 $client->submit($form, $data);
428
429 $this->assertEquals(302, $client->getResponse()->getStatusCode());
430
431 $crawler = $client->followRedirect();
432
433 $this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text')));
434 $this->assertContains('RSS information updated', $alert[0]);
435 }
436
437 public function dataForRssFailed()
438 {
439 return array(
440 array(
441 array(
442 'rss_config[rss_limit]' => 0,
443 ),
444 'This value should be 1 or more.',
445 ),
446 array(
447 array(
448 'rss_config[rss_limit]' => 1000000000000,
449 ),
450 'This will certainly kill the app',
451 ),
452 );
453 }
454
455 /**
456 * @dataProvider dataForRssFailed
457 */
458 public function testRssFailed($data, $expectedMessage)
459 {
460 $this->logInAs('admin');
461 $client = $this->getClient();
462
463 $crawler = $client->request('GET', '/config');
464
465 $this->assertEquals(200, $client->getResponse()->getStatusCode());
466
467 $form = $crawler->filter('button[id=rss_config_save]')->form();
468
469 $crawler = $client->submit($form, $data);
470
471 $this->assertEquals(200, $client->getResponse()->getStatusCode());
472
473 $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(array('_text')));
474 $this->assertContains($expectedMessage, $alert[0]);
475 }
350} 476}
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
index 99a3a945..1a0d586c 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
@@ -60,7 +60,7 @@ class EntryControllerTest extends WallabagTestCase
60 $form = $crawler->filter('button[type=submit]')->form(); 60 $form = $crawler->filter('button[type=submit]')->form();
61 61
62 $data = array( 62 $data = array(
63 'entry[url]' => 'https://www.mailjet.com/blog/mailjet-zapier-integrations-made-easy/', 63 'entry[url]' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
64 ); 64 );
65 65
66 $client->submit($form, $data); 66 $client->submit($form, $data);
@@ -70,7 +70,7 @@ class EntryControllerTest extends WallabagTestCase
70 $crawler = $client->followRedirect(); 70 $crawler = $client->followRedirect();
71 71
72 $this->assertGreaterThan(1, $alert = $crawler->filter('h2 a')->extract(array('_text'))); 72 $this->assertGreaterThan(1, $alert = $crawler->filter('h2 a')->extract(array('_text')));
73 $this->assertContains('Mailjet', $alert[0]); 73 $this->assertContains('Google', $alert[0]);
74 } 74 }
75 75
76 public function testArchive() 76 public function testArchive()
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php
new file mode 100644
index 00000000..8f627b4b
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php
@@ -0,0 +1,126 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests\Controller;
4
5use Wallabag\CoreBundle\Tests\WallabagTestCase;
6
7class RssControllerTest extends WallabagTestCase
8{
9 public function validateDom($xml, $nb = null)
10 {
11 $doc = new \DOMDocument();
12 $doc->loadXML($xml);
13
14 $xpath = new \DOMXpath($doc);
15
16 if (null === $nb) {
17 $this->assertGreaterThan(0, $xpath->query('//item')->length);
18 } else {
19 $this->assertEquals($nb, $xpath->query('//item')->length);
20 }
21
22 $this->assertEquals(1, $xpath->query('/rss')->length);
23 $this->assertEquals(1, $xpath->query('/rss/channel')->length);
24
25 foreach ($xpath->query('//item') as $item) {
26 $this->assertEquals(1, $xpath->query('title', $item)->length);
27 $this->assertEquals(1, $xpath->query('source', $item)->length);
28 $this->assertEquals(1, $xpath->query('link', $item)->length);
29 $this->assertEquals(1, $xpath->query('guid', $item)->length);
30 $this->assertEquals(1, $xpath->query('pubDate', $item)->length);
31 $this->assertEquals(1, $xpath->query('description', $item)->length);
32 }
33 }
34
35 public function dataForBadUrl()
36 {
37 return array(
38 array(
39 '/admin/YZIOAUZIAO/unread.xml'
40 ),
41 array(
42 '/wallace/YZIOAUZIAO/starred.xml'
43 ),
44 array(
45 '/wallace/YZIOAUZIAO/archives.xml'
46 ),
47 );
48 }
49
50 /**
51 * @dataProvider dataForBadUrl
52 */
53 public function testBadUrl($url)
54 {
55 $client = $this->getClient();
56
57 $client->request('GET', $url);
58
59 $this->assertEquals(404, $client->getResponse()->getStatusCode());
60 }
61
62 public function testUnread()
63 {
64 $client = $this->getClient();
65 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
66 $user = $em
67 ->getRepository('WallabagCoreBundle:User')
68 ->findOneByUsername('admin');
69
70 $config = $user->getConfig();
71 $config->setRssToken('SUPERTOKEN');
72 $config->setRssLimit(2);
73 $em->persist($config);
74 $em->flush();
75
76 $client->request('GET', '/admin/SUPERTOKEN/unread.xml');
77
78 $this->assertEquals(200, $client->getResponse()->getStatusCode());
79
80 $this->validateDom($client->getResponse()->getContent(), 2);
81 }
82
83 public function testStarred()
84 {
85 $client = $this->getClient();
86 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
87 $user = $em
88 ->getRepository('WallabagCoreBundle:User')
89 ->findOneByUsername('admin');
90
91 $config = $user->getConfig();
92 $config->setRssToken('SUPERTOKEN');
93 $config->setRssLimit(1);
94 $em->persist($config);
95 $em->flush();
96
97 $client = $this->getClient();
98 $client->request('GET', '/admin/SUPERTOKEN/starred.xml');
99
100 $this->assertEquals(200, $client->getResponse()->getStatusCode(), 1);
101
102 $this->validateDom($client->getResponse()->getContent());
103 }
104
105 public function testArchives()
106 {
107 $client = $this->getClient();
108 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
109 $user = $em
110 ->getRepository('WallabagCoreBundle:User')
111 ->findOneByUsername('admin');
112
113 $config = $user->getConfig();
114 $config->setRssToken('SUPERTOKEN');
115 $config->setRssLimit(null);
116 $em->persist($config);
117 $em->flush();
118
119 $client = $this->getClient();
120 $client->request('GET', '/admin/SUPERTOKEN/archive.xml');
121
122 $this->assertEquals(200, $client->getResponse()->getStatusCode());
123
124 $this->validateDom($client->getResponse()->getContent());
125 }
126}
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/WallabagRestControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/WallabagRestControllerTest.php
index 0eca7cde..c9907065 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/WallabagRestControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/WallabagRestControllerTest.php
@@ -105,7 +105,7 @@ class WallabagRestControllerTest extends WallabagTestCase
105 105
106 $this->assertGreaterThanOrEqual(1, count(json_decode($client->getResponse()->getContent()))); 106 $this->assertGreaterThanOrEqual(1, count(json_decode($client->getResponse()->getContent())));
107 107
108 $this->assertContains('Mailjet', $client->getResponse()->getContent()); 108 $this->assertContains('Google', $client->getResponse()->getContent());
109 109
110 $this->assertTrue( 110 $this->assertTrue(
111 $client->getResponse()->headers->contains( 111 $client->getResponse()->headers->contains(
diff --git a/src/Wallabag/CoreBundle/Tests/Mock/InstallCommandMock.php b/src/Wallabag/CoreBundle/Tests/Mock/InstallCommandMock.php
new file mode 100644
index 00000000..69bc48e0
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/Mock/InstallCommandMock.php
@@ -0,0 +1,22 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests\Mock;
4
5use Wallabag\CoreBundle\Command\InstallCommand;
6
7/**
8 * This mock aims to speed the test of InstallCommand by avoid calling external command
9 * like all doctrine commands.
10 *
11 * This speed the test but as a downside, it doesn't allow to fully test the InstallCommand
12 *
13 * Launching tests to avoid doctrine command:
14 * phpunit --exclude-group command-doctrine
15 */
16class InstallCommandMock extends InstallCommand
17{
18 protected function runCommand($command, $parameters = array())
19 {
20 return $this;
21 }
22}
diff --git a/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php b/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php
new file mode 100644
index 00000000..ebb550b5
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php
@@ -0,0 +1,220 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests\Command;
4
5use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6use Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
8use Symfony\Component\HttpFoundation\Request;
9use Wallabag\CoreBundle\Entity\User;
10
11class UsernameRssTokenConverterTest extends KernelTestCase
12{
13 public function testSupportsWithNoRegistry()
14 {
15 $params = new ParamConverter(array());
16 $converter = new UsernameRssTokenConverter();
17
18 $this->assertFalse($converter->supports($params));
19 }
20
21 public function testSupportsWithNoRegistryManagers()
22 {
23 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
24 ->disableOriginalConstructor()
25 ->getMock();
26
27 $registry->expects($this->once())
28 ->method('getManagers')
29 ->will($this->returnValue(array()));
30
31 $params = new ParamConverter(array());
32 $converter = new UsernameRssTokenConverter($registry);
33
34 $this->assertFalse($converter->supports($params));
35 }
36
37 public function testSupportsWithNoConfigurationClass()
38 {
39 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
40 ->disableOriginalConstructor()
41 ->getMock();
42
43 $registry->expects($this->once())
44 ->method('getManagers')
45 ->will($this->returnValue(array('default' => null)));
46
47 $params = new ParamConverter(array());
48 $converter = new UsernameRssTokenConverter($registry);
49
50 $this->assertFalse($converter->supports($params));
51 }
52
53 public function testSupportsWithNotTheGoodClass()
54 {
55 $meta = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')
56 ->disableOriginalConstructor()
57 ->getMock();
58
59 $meta->expects($this->once())
60 ->method('getName')
61 ->will($this->returnValue('nothingrelated'));
62
63 $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')
64 ->disableOriginalConstructor()
65 ->getMock();
66
67 $em->expects($this->once())
68 ->method('getClassMetadata')
69 ->with('superclass')
70 ->will($this->returnValue($meta));
71
72 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
73 ->disableOriginalConstructor()
74 ->getMock();
75
76 $registry->expects($this->once())
77 ->method('getManagers')
78 ->will($this->returnValue(array('default' => null)));
79
80 $registry->expects($this->once())
81 ->method('getManagerForClass')
82 ->with('superclass')
83 ->will($this->returnValue($em));
84
85 $params = new ParamConverter(array('class' => 'superclass'));
86 $converter = new UsernameRssTokenConverter($registry);
87
88 $this->assertFalse($converter->supports($params));
89 }
90
91 public function testSupportsWithGoodClass()
92 {
93 $meta = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')
94 ->disableOriginalConstructor()
95 ->getMock();
96
97 $meta->expects($this->once())
98 ->method('getName')
99 ->will($this->returnValue('Wallabag\CoreBundle\Entity\User'));
100
101 $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')
102 ->disableOriginalConstructor()
103 ->getMock();
104
105 $em->expects($this->once())
106 ->method('getClassMetadata')
107 ->with('WallabagCoreBundle:User')
108 ->will($this->returnValue($meta));
109
110 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
111 ->disableOriginalConstructor()
112 ->getMock();
113
114 $registry->expects($this->once())
115 ->method('getManagers')
116 ->will($this->returnValue(array('default' => null)));
117
118 $registry->expects($this->once())
119 ->method('getManagerForClass')
120 ->with('WallabagCoreBundle:User')
121 ->will($this->returnValue($em));
122
123 $params = new ParamConverter(array('class' => 'WallabagCoreBundle:User'));
124 $converter = new UsernameRssTokenConverter($registry);
125
126 $this->assertTrue($converter->supports($params));
127 }
128
129 /**
130 * @expectedException InvalidArgumentException
131 * @expectedExceptionMessage Route attribute is missing
132 */
133 public function testApplyEmptyRequest()
134 {
135 $params = new ParamConverter(array());
136 $converter = new UsernameRssTokenConverter();
137
138 $converter->apply(new Request(), $params);
139 }
140
141 /**
142 * @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException
143 * @expectedExceptionMessage User not found
144 */
145 public function testApplyUserNotFound()
146 {
147 $repo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\UserRepository')
148 ->disableOriginalConstructor()
149 ->getMock();
150
151 $repo->expects($this->once())
152 ->method('findOneByUsernameAndRsstoken')
153 ->with('test', 'test')
154 ->will($this->returnValue(null));
155
156 $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')
157 ->disableOriginalConstructor()
158 ->getMock();
159
160 $em->expects($this->once())
161 ->method('getRepository')
162 ->with('WallabagCoreBundle:User')
163 ->will($this->returnValue($repo));
164
165 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
166 ->disableOriginalConstructor()
167 ->getMock();
168
169 $registry->expects($this->once())
170 ->method('getManagerForClass')
171 ->with('WallabagCoreBundle:User')
172 ->will($this->returnValue($em));
173
174 $params = new ParamConverter(array('class' => 'WallabagCoreBundle:User'));
175 $converter = new UsernameRssTokenConverter($registry);
176 $request = new Request(array(), array(), array('username' => 'test', 'token' => 'test'));
177
178 $converter->apply($request, $params);
179 }
180
181 public function testApplyUserFound()
182 {
183 $user = new User();
184
185 $repo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\UserRepository')
186 ->disableOriginalConstructor()
187 ->getMock();
188
189 $repo->expects($this->once())
190 ->method('findOneByUsernameAndRsstoken')
191 ->with('test', 'test')
192 ->will($this->returnValue($user));
193
194 $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')
195 ->disableOriginalConstructor()
196 ->getMock();
197
198 $em->expects($this->once())
199 ->method('getRepository')
200 ->with('WallabagCoreBundle:User')
201 ->will($this->returnValue($repo));
202
203 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
204 ->disableOriginalConstructor()
205 ->getMock();
206
207 $registry->expects($this->once())
208 ->method('getManagerForClass')
209 ->with('WallabagCoreBundle:User')
210 ->will($this->returnValue($em));
211
212 $params = new ParamConverter(array('class' => 'WallabagCoreBundle:User', 'name' => 'user'));
213 $converter = new UsernameRssTokenConverter($registry);
214 $request = new Request(array(), array(), array('username' => 'test', 'token' => 'test'));
215
216 $converter->apply($request, $params);
217
218 $this->assertEquals($user, $request->attributes->get('user'));
219 }
220}
diff --git a/src/Wallabag/CoreBundle/Tools/Utils.php b/src/Wallabag/CoreBundle/Tools/Utils.php
new file mode 100644
index 00000000..de97c796
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tools/Utils.php
@@ -0,0 +1,27 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tools;
4
5class Utils
6{
7 /**
8 * Generate a token used for RSS
9 *
10 * @return string
11 */
12 public static function generateToken()
13 {
14 if (ini_get('open_basedir') === '') {
15 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
16 // alternative to /dev/urandom for Windows
17 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
18 } else {
19 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
20 }
21 } else {
22 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
23 }
24
25 return str_replace('+', '', $token);
26 }
27}