theme: baggy
language: en_US
from_email: no-reply@wallabag.org
+ rss_limit: 50
$config = new Config($user);
$config->setTheme($this->getContainer()->getParameter('theme'));
$config->setItemsPerPage($this->getContainer()->getParameter('items_on_page'));
+ $config->setRssLimit($this->getContainer()->getParameter('rss_limit'));
$config->setLanguage($this->getContainer()->getParameter('language'));
$em->persist($config);
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\JsonResponse;
use Wallabag\CoreBundle\Entity\Config;
use Wallabag\CoreBundle\Entity\User;
use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
use Wallabag\CoreBundle\Form\Type\UserType;
use Wallabag\CoreBundle\Form\Type\NewUserType;
+use Wallabag\CoreBundle\Form\Type\RssType;
+use Wallabag\CoreBundle\Tools\Utils;
class ConfigController extends Controller
{
return $this->redirect($this->generateUrl('config'));
}
+ // handle rss information
+ $rssForm = $this->createForm(new RssType(), $config);
+ $rssForm->handleRequest($request);
+
+ if ($rssForm->isValid()) {
+ $em->persist($config);
+ $em->flush();
+
+ $this->get('session')->getFlashBag()->add(
+ 'notice',
+ 'RSS information updated'
+ );
+
+ return $this->redirect($this->generateUrl('config'));
+ }
+
// handle adding new user
$newUser = new User();
$newUserForm = $this->createForm(new NewUserType(), $newUser);
$config = new Config($newUser);
$config->setTheme($this->container->getParameter('theme'));
$config->setItemsPerPage($this->container->getParameter('items_on_page'));
+ $config->setRssLimit($this->getContainer()->getParameter('rss_limit'));
$config->setLanguage($this->container->getParameter('language'));
$em->persist($config);
}
return $this->render('WallabagCoreBundle:Config:index.html.twig', array(
- 'configForm' => $configForm->createView(),
- 'pwdForm' => $pwdForm->createView(),
- 'userForm' => $userForm->createView(),
- 'newUserForm' => $newUserForm->createView(),
+ 'form' => array(
+ 'config' => $configForm->createView(),
+ 'rss' => $rssForm->createView(),
+ 'pwd' => $pwdForm->createView(),
+ 'user' => $userForm->createView(),
+ 'new_user' => $newUserForm->createView(),
+ ),
+ 'rss' => array(
+ 'username' => $user->getUsername(),
+ 'token' => $config->getRssToken(),
+ )
));
}
+ /**
+ * @param Request $request
+ *
+ * @Route("/generate-token", name="generate_token")
+ *
+ * @return JsonResponse
+ */
+ public function generateTokenAction(Request $request)
+ {
+ $config = $this->getConfig();
+ $config->setRssToken(Utils::generateToken());
+
+ $em = $this->getDoctrine()->getManager();
+ $em->persist($config);
+ $em->flush();
+
+ if ($request->isXmlHttpRequest()) {
+ return new JsonResponse(array('token' => $config->getRssToken()));
+ }
+
+ return $request->headers->get('referer') ? $this->redirect($request->headers->get('referer')) : $this->redirectToRoute('config');
+ }
+
/**
* Retrieve config for the current user.
* If no config were found, create a new one.
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Controller;
+
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Wallabag\CoreBundle\Entity\User;
+use Wallabag\CoreBundle\Entity\Entry;
+
+class RssController extends Controller
+{
+ /**
+ * Shows unread entries for current user
+ *
+ * @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"})
+ * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function showUnreadAction(User $user)
+ {
+ $entries = $this->getDoctrine()
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findUnreadByUser(
+ $user->getId(),
+ 0,
+ $user->getConfig()->getRssLimit() ?: $this->getContainer()->getParameter('rss_limit')
+ );
+
+ return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
+ 'type' => 'unread',
+ 'entries' => $entries,
+ ));
+ }
+
+ /**
+ * Shows read entries for current user
+ *
+ * @Route("/{username}/{token}/archive.xml", name="archive_rss")
+ * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function showArchiveAction(User $user)
+ {
+ $entries = $this->getDoctrine()
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findArchiveByUser(
+ $user->getId(),
+ 0,
+ $user->getConfig()->getRssLimit() ?: $this->getContainer()->getParameter('rss_limit')
+ );
+
+ return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
+ 'type' => 'archive',
+ 'entries' => $entries,
+ ));
+ }
+
+ /**
+ * Shows starred entries for current user
+ *
+ * @Route("/{username}/{token}/starred.xml", name="starred_rss")
+ * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function showStarredAction(User $user)
+ {
+ $entries = $this->getDoctrine()
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findStarredByUser(
+ $user->getId(),
+ 0,
+ $user->getConfig()->getRssLimit() ?: $this->getContainer()->getParameter('rss_limit')
+ );
+
+ return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
+ 'type' => 'starred',
+ 'entries' => $entries,
+ ));
+ }
+}
private $theme;
/**
- * @var string
+ * @var integer
*
* @Assert\NotBlank()
* @ORM\Column(name="items_per_page", type="integer", nullable=false)
*/
- private $items_per_page;
+ private $itemsPerPage;
/**
* @var string
*/
private $language;
+ /**
+ * @var string
+ *
+ * @ORM\Column(name="rss_token", type="string", nullable=true)
+ */
+ private $rssToken;
+
+ /**
+ * @var integer
+ *
+ * @ORM\Column(name="rss_limit", type="integer", nullable=true)
+ */
+ private $rssLimit;
+
/**
* @ORM\OneToOne(targetEntity="User", inversedBy="config")
*/
public function __construct(User $user)
{
$this->user = $user;
- $this->items_per_page = 12;
- $this->language = 'en_US';
}
/**
}
/**
- * Set items_per_page
+ * Set itemsPerPage
*
* @param integer $itemsPerPage
* @return Config
*/
public function setItemsPerPage($itemsPerPage)
{
- $this->items_per_page = $itemsPerPage;
+ $this->itemsPerPage = $itemsPerPage;
return $this;
}
/**
- * Get items_per_page
+ * Get itemsPerPage
*
* @return integer
*/
public function getItemsPerPage()
{
- return $this->items_per_page;
+ return $this->itemsPerPage;
}
/**
{
return $this->user;
}
+
+ /**
+ * Set rssToken
+ *
+ * @param string $rssToken
+ * @return Config
+ */
+ public function setRssToken($rssToken)
+ {
+ $this->rssToken = $rssToken;
+
+ return $this;
+ }
+
+ /**
+ * Get rssToken
+ *
+ * @return string
+ */
+ public function getRssToken()
+ {
+ return $this->rssToken;
+ }
+
+ /**
+ * Set rssLimit
+ *
+ * @param string $rssLimit
+ * @return Config
+ */
+ public function setRssLimit($rssLimit)
+ {
+ $this->rssLimit = $rssLimit;
+
+ return $this;
+ }
+
+ /**
+ * Get rssLimit
+ *
+ * @return string
+ */
+ public function getRssLimit()
+ {
+ return $this->rssLimit;
+ }
}
* User
*
* @ORM\Table(name="user")
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\UserRepository")
* @ORM\HasLifecycleCallbacks()
* @ExclusionPolicy("all")
*/
--- /dev/null
+<?php
+namespace Wallabag\CoreBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+
+class RssType extends AbstractType
+{
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->add('rss_limit', 'text')
+ ->add('save', 'submit')
+ ;
+ }
+
+ public function setDefaultOptions(OptionsResolverInterface $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'data_class' => 'Wallabag\CoreBundle\Entity\Config',
+ ));
+ }
+
+ public function getName()
+ {
+ return 'rss_config';
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\ParamConverter;
+
+use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
+use Doctrine\Common\Persistence\ManagerRegistry;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Wallabag\CoreBundle\Entity\User;
+
+/**
+ * ParamConverter used in the RSS controller to retrieve the right user according to
+ * username & token given in the url.
+ *
+ * @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter
+ */
+class UsernameRssTokenConverter implements ParamConverterInterface
+{
+ private $registry;
+
+ /**
+ * @param ManagerRegistry $registry Manager registry
+ */
+ public function __construct(ManagerRegistry $registry = null)
+ {
+ $this->registry = $registry;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Check, if object supported by our converter
+ */
+ public function supports(ParamConverter $configuration)
+ {
+ // If there is no manager, this means that only Doctrine DBAL is configured
+ // In this case we can do nothing and just return
+ if (null === $this->registry || !count($this->registry->getManagers())) {
+ return false;
+ }
+
+ // Check, if option class was set in configuration
+ if (null === $configuration->getClass()) {
+ return false;
+ }
+
+ // Get actual entity manager for class
+ $em = $this->registry->getManagerForClass($configuration->getClass());
+
+ // Check, if class name is what we need
+ if ('Wallabag\CoreBundle\Entity\User' !== $em->getClassMetadata($configuration->getClass())->getName()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Applies converting
+ *
+ * @throws \InvalidArgumentException When route attributes are missing
+ * @throws NotFoundHttpException When object not found
+ */
+ public function apply(Request $request, ParamConverter $configuration)
+ {
+ $username = $request->attributes->get('username');
+ $rssToken = $request->attributes->get('token');
+
+ // Check, if route attributes exists
+ if (null === $username || null === $rssToken) {
+ throw new \InvalidArgumentException('Route attribute is missing');
+ }
+
+ // Get actual entity manager for class
+ $em = $this->registry->getManagerForClass($configuration->getClass());
+
+ $userRepository = $em->getRepository($configuration->getClass());
+
+ // Try to find user by its username and config rss_token
+ $user = $userRepository->findOneByUsernameAndRsstoken($username, $rssToken);
+
+ if (null === $user || !($user instanceof User)) {
+ throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass()));
+ }
+
+ // Map found user to the route's parameter
+ $request->attributes->set($configuration->getName(), $user);
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Repository;
+
+use Doctrine\ORM\EntityRepository;
+
+class UserRepository extends EntityRepository
+{
+ /**
+ * Find a user by its username and rss roken
+ *
+ * @param string $username
+ * @param string $rssToken
+ *
+ * @return User|null
+ */
+ public function findOneByUsernameAndRsstoken($username, $rssToken)
+ {
+ return $this->createQueryBuilder('u')
+ ->leftJoin('u.config', 'c')
+ ->where('c.rssToken = :rss_token')->setParameter('rss_token', $rssToken)
+ ->andWhere('u.username = :username')->setParameter('username', $username)
+ ->getQuery()
+ ->getOneOrNullResult();
+ }
+}
- @doctrine
tags:
- { name: form.type, alias: forgot_password }
+
+ wallabag_core.param_converter.username_rsstoken_converter:
+ class: Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter
+ tags:
+ - { name: request.param_converter, converter: username_rsstoken_converter }
+ arguments:
+ - @doctrine
{% block content %}
<h2>{% trans %}Wallabag configuration{% endtrans %}</h2>
- <form action="{{ path('config') }}" method="post" {{ form_enctype(configForm) }}>
- {{ form_errors(configForm) }}
+ <form action="{{ path('config') }}" method="post" {{ form_enctype(form.config) }}>
+ {{ form_errors(form.config) }}
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(configForm.theme) }}
- {{ form_errors(configForm.theme) }}
- {{ form_widget(configForm.theme) }}
+ {{ form_label(form.config.theme) }}
+ {{ form_errors(form.config.theme) }}
+ {{ form_widget(form.config.theme) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(configForm.items_per_page) }}
- {{ form_errors(configForm.items_per_page) }}
- {{ form_widget(configForm.items_per_page) }}
+ {{ form_label(form.config.items_per_page) }}
+ {{ form_errors(form.config.items_per_page) }}
+ {{ form_widget(form.config.items_per_page) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(configForm.language) }}
- {{ form_errors(configForm.language) }}
- {{ form_widget(configForm.language) }}
+ {{ form_label(form.config.language) }}
+ {{ form_errors(form.config.language) }}
+ {{ form_widget(form.config.language) }}
</div>
</fieldset>
- {{ form_rest(configForm) }}
+ {{ form_rest(form.config) }}
+ </form>
+
+ <h2>{% trans %}RSS configuration{% endtrans %}</h2>
+
+ <form action="{{ path('config') }}" method="post" {{ form_enctype(form.rss) }}>
+ {{ form_errors(form.rss) }}
+
+ <fieldset class="w500p inline">
+ <div class="row">
+ <label>Rss token</label>
+ {% if rss.token %}
+ {{ rss.token }}
+ {% else %}
+ <em>No token</em>
+ {% endif %}
+ –
+ <a href="{{ path('generate_token') }}">Regenerate ?</a>
+ </div>
+ </fieldset>
+
+ <fieldset class="w500p inline">
+ <div class="row">
+ <label>Rss links:</label>
+ <ul>
+ <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">unread</a></li>
+ <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">fav</a></li>
+ <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">archives</a></li>
+ </ul>
+ </div>
+ </fieldset>
+
+ <fieldset class="w500p inline">
+ <div class="row">
+ {{ form_label(form.rss.rss_limit) }}
+ {{ form_errors(form.rss.rss_limit) }}
+ {{ form_widget(form.rss.rss_limit) }}
+ </div>
+ </fieldset>
+
+ {{ form_rest(form.rss) }}
</form>
<h2>{% trans %}User information{% endtrans %}</h2>
- <form action="{{ path('config') }}" method="post" {{ form_enctype(userForm) }}>
- {{ form_errors(userForm) }}
+ <form action="{{ path('config') }}" method="post" {{ form_enctype(form.user) }}>
+ {{ form_errors(form.user) }}
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(userForm.username) }}
- {{ form_errors(userForm.username) }}
- {{ form_widget(userForm.username) }}
+ {{ form_label(form.user.username) }}
+ {{ form_errors(form.user.username) }}
+ {{ form_widget(form.user.username) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(userForm.name) }}
- {{ form_errors(userForm.name) }}
- {{ form_widget(userForm.name) }}
+ {{ form_label(form.user.name) }}
+ {{ form_errors(form.user.name) }}
+ {{ form_widget(form.user.name) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(userForm.email) }}
- {{ form_errors(userForm.email) }}
- {{ form_widget(userForm.email) }}
+ {{ form_label(form.user.email) }}
+ {{ form_errors(form.user.email) }}
+ {{ form_widget(form.user.email) }}
</div>
</fieldset>
- {{ form_rest(userForm) }}
+ {{ form_rest(form.user) }}
</form>
<h2>{% trans %}Change your password{% endtrans %}</h2>
- <form action="{{ path('config') }}" method="post" {{ form_enctype(pwdForm) }}>
- {{ form_errors(pwdForm) }}
+ <form action="{{ path('config') }}" method="post" {{ form_enctype(form.pwd) }}>
+ {{ form_errors(form.pwd) }}
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(pwdForm.old_password) }}
- {{ form_errors(pwdForm.old_password) }}
- {{ form_widget(pwdForm.old_password) }}
+ {{ form_label(form.pwd.old_password) }}
+ {{ form_errors(form.pwd.old_password) }}
+ {{ form_widget(form.pwd.old_password) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(pwdForm.new_password.first) }}
- {{ form_errors(pwdForm.new_password.first) }}
- {{ form_widget(pwdForm.new_password.first) }}
+ {{ form_label(form.pwd.new_password.first) }}
+ {{ form_errors(form.pwd.new_password.first) }}
+ {{ form_widget(form.pwd.new_password.first) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(pwdForm.new_password.second) }}
- {{ form_errors(pwdForm.new_password.second) }}
- {{ form_widget(pwdForm.new_password.second) }}
+ {{ form_label(form.pwd.new_password.second) }}
+ {{ form_errors(form.pwd.new_password.second) }}
+ {{ form_widget(form.pwd.new_password.second) }}
</div>
</fieldset>
- {{ form_rest(pwdForm) }}
+ {{ form_rest(form.pwd) }}
</form>
<h2>{% trans %}Add a user{% endtrans %}</h2>
- <form action="{{ path('config') }}" method="post" {{ form_enctype(newUserForm) }}>
- {{ form_errors(newUserForm) }}
+ <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_user) }}>
+ {{ form_errors(form.new_user) }}
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(newUserForm.username) }}
- {{ form_errors(newUserForm.username) }}
- {{ form_widget(newUserForm.username) }}
+ {{ form_label(form.new_user.username) }}
+ {{ form_errors(form.new_user.username) }}
+ {{ form_widget(form.new_user.username) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(newUserForm.password) }}
- {{ form_errors(newUserForm.password) }}
- {{ form_widget(newUserForm.password) }}
+ {{ form_label(form.new_user.password) }}
+ {{ form_errors(form.new_user.password) }}
+ {{ form_widget(form.new_user.password) }}
</div>
</fieldset>
<fieldset class="w500p inline">
<div class="row">
- {{ form_label(newUserForm.email) }}
- {{ form_errors(newUserForm.email) }}
- {{ form_widget(newUserForm.email) }}
+ {{ form_label(form.new_user.email) }}
+ {{ form_errors(form.new_user.email) }}
+ {{ form_widget(form.new_user.email) }}
</div>
</fieldset>
- {{ form_rest(newUserForm) }}
+ {{ form_rest(form.new_user) }}
</form>
{% endblock %}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<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/">
+ <channel>
+ <title>wallabag — {{type}} feed</title>
+ <link>{{ url('unread') }}</link>
+ <pubDate>{{ "now"|date('D, d M Y H:i:s') }}</pubDate>
+ <generator>wallabag</generator>
+ <description>wallabag {{type}} elements</description>
+
+ {% for entry in entries %}
+
+ <item>
+ <title><![CDATA[{{ entry.title }}]]></title>
+ <source url="{{ url('view', { 'id': entry.id }) }}">wallabag</source>
+ <link>{{ url('view', { 'id': entry.id }) }}</link>
+ <guid>{{ url('view', { 'id': entry.id }) }}</guid>
+ <pubDate>{{ entry.createdAt|date('D, d M Y H:i:s') }}</pubDate>
+ <description>
+ <![CDATA[
+ {%- if entry.content| readingTime > 0 %}
+ {% trans %}estimated reading time :{% endtrans %} {{ entry.content| readingTime }} min
+ {% else -%}
+ {% trans %}estimated reading time :{% endtrans %} < 1 min
+ {% endif -%}
+
+ {{ entry.content -}}
+ ]]>
+ </description>
+ </item>
+
+ {% endfor %}
+
+ </channel>
+</rss>
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Tools;
+
+class Utils
+{
+ /**
+ * Generate a token used for RSS
+ *
+ * @return string
+ */
+ public static function generateToken()
+ {
+ if (ini_get('open_basedir') === '') {
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ // alternative to /dev/urandom for Windows
+ $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
+ } else {
+ $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
+ }
+ } else {
+ $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
+ }
+
+ return str_replace('+', '', $token);
+ }
+}