+ */
+ private function getRulesForUser(User $user)
+ {
+ return $user->getConfig()->getTaggingRules();
+ }
+}
diff --git a/src/Wallabag/CoreBundle/Operator/Doctrine/Matches.php b/src/Wallabag/CoreBundle/Operator/Doctrine/Matches.php
new file mode 100644
index 000000000..e6bb03b12
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Operator/Doctrine/Matches.php
@@ -0,0 +1,25 @@
+
+ {% trans %}Tagging rules{% endtrans %}
+
+
+ {% for tagging_rule in app.user.config.taggingRules %}
+
+ if « {{ tagging_rule.rule }} » then tag as « {{ tagging_rule.tags|join(', ') }} »
+
+
+ {% endfor %}
+
+
+
+
{% if is_granted('ROLE_SUPER_ADMIN') %}
{% trans %}Add a user{% endtrans %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
index 8f121a2b7..d060311d4 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
@@ -15,8 +15,9 @@
{% trans %}RSS{% endtrans %}
{% trans %}User information{% endtrans %}
{% trans %}Password{% endtrans %}
+ {% trans %}Tagging rules{% endtrans %}
{% if is_granted('ROLE_SUPER_ADMIN') %}
- {% trans %}Add a user{% endtrans %}
+ {% trans %}Add a user{% endtrans %}
{% endif %}
@@ -183,8 +184,155 @@
- {% if is_granted('ROLE_SUPER_ADMIN') %}
+
+
+ {{ form_start(form.new_tagging_rule) }}
+ {{ form_errors(form.new_tagging_rule) }}
+
+
+
+ {{ form_label(form.new_tagging_rule.rule) }}
+ {{ form_errors(form.new_tagging_rule.rule) }}
+ {{ form_widget(form.new_tagging_rule.rule) }}
+
+
+
+
+
+ {{ form_label(form.new_tagging_rule.tags) }}
+ {{ form_errors(form.new_tagging_rule.tags) }}
+ {{ form_widget(form.new_tagging_rule.tags) }}
+
+
+
+
{{ form_rest(form.new_tagging_rule) }}
+
+ {% trans %}Save{% endtrans %}
+
+
+
+
+
+
+ {% if is_granted('ROLE_SUPER_ADMIN') %}
+
{{ form_start(form.new_user) }}
{{ form_errors(form.new_user) }}
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
index 7085151ae..7b32354f8 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
@@ -479,4 +479,59 @@ class ConfigControllerTest extends WallabagCoreTestCase
$this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(array('_text')));
$this->assertContains($expectedMessage, $alert[0]);
}
+
+ public function testTaggingRuleCreation()
+ {
+ $this->logInAs('admin');
+ $client = $this->getClient();
+
+ $crawler = $client->request('GET', '/config');
+
+ $this->assertTrue($client->getResponse()->isSuccessful());
+
+ $form = $crawler->filter('button[id=tagging_rule_save]')->form();
+
+ $data = array(
+ 'tagging_rule[rule]' => 'readingTime <= 3',
+ 'tagging_rule[tags]' => 'short reading',
+ );
+
+ $client->submit($form, $data);
+
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->followRedirect();
+
+ $this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text')));
+ $this->assertContains('Tagging rules updated', $alert[0]);
+
+ $deleteLink = $crawler->filter('.delete')->last()->link();
+
+ $crawler = $client->click($deleteLink);
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->followRedirect();
+ $this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text')));
+ $this->assertContains('Tagging rule deleted', $alert[0]);
+ }
+
+ public function dataForTaggingRuleFailed()
+ {
+ return array(
+ array(
+ array(
+ 'rss_config[rule]' => 'unknownVar <= 3',
+ 'rss_config[tags]' => 'cool tag',
+ ),
+ 'The variable « unknownVar » does not exist.',
+ ),
+ array(
+ array(
+ 'rss_config[rule]' => 'length(domainName) <= 42',
+ 'rss_config[tags]' => 'cool tag',
+ ),
+ 'The operator « length » does not exist.',
+ ),
+ );
+ }
}
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
index 56b4c9e41..af62aee8d 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
@@ -102,6 +102,44 @@ class EntryControllerTest extends WallabagCoreTestCase
$this->assertContains('Google', $alert[0]);
}
+ /**
+ * This test will require an internet connection.
+ */
+ public function testPostNewThatWillBeTaggued()
+ {
+ $this->logInAs('admin');
+ $client = $this->getClient();
+
+ $crawler = $client->request('GET', '/new');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $form = $crawler->filter('button[type=submit]')->form();
+
+ $data = array(
+ 'entry[url]' => $url = 'https://github.com/wallabag/wallabag',
+ );
+
+ $client->submit($form, $data);
+
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->followRedirect();
+
+ $em = $client->getContainer()
+ ->get('doctrine.orm.entity_manager');
+ $entry = $em
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUrl($url);
+ $tags = $entry->getTags();
+
+ $this->assertCount(1, $tags);
+ $this->assertEquals('wallabag', $tags[0]->getLabel());
+
+ $em->remove($entry);
+ $em->flush();
+ }
+
public function testArchive()
{
$this->logInAs('admin');
diff --git a/src/Wallabag/CoreBundle/Tests/Form/DataTransformer/StringToListTransformerTest.php b/src/Wallabag/CoreBundle/Tests/Form/DataTransformer/StringToListTransformerTest.php
new file mode 100644
index 000000000..d114e5f35
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/Form/DataTransformer/StringToListTransformerTest.php
@@ -0,0 +1,50 @@
+assertSame($expectedResult, $transformer->transform($inputData));
+ }
+
+ public function transformProvider()
+ {
+ return array(
+ array( null, '' ),
+ array( array(), '' ),
+ array( array('single value'), 'single value' ),
+ array( array('first value', 'second value'), 'first value,second value' ),
+ );
+ }
+
+ /**
+ * @dataProvider reverseTransformProvider
+ */
+ public function testReverseTransformWithValidData($inputData, $expectedResult)
+ {
+ $transformer = new StringToListTransformer();
+
+ $this->assertSame($expectedResult, $transformer->reverseTransform($inputData));
+ }
+
+ public function reverseTransformProvider()
+ {
+ return array(
+ array( null, null ),
+ array( '', array() ),
+ array( 'single value', array('single value') ),
+ array( 'first value,second value', array('first value', 'second value') ),
+ array( 'first value, second value', array('first value', 'second value') ),
+ array( 'first value, , second value', array('first value', 'second value') ),
+ );
+ }
+}
diff --git a/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php b/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php
index 4bce4708f..ef7cbd5b2 100644
--- a/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php
@@ -2,6 +2,9 @@
namespace Wallabag\CoreBundle\Tests\Helper;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Psr\Log\NullLogger;
+
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\UserBundle\Entity\User;
use Wallabag\CoreBundle\Helper\ContentProxy;
@@ -10,6 +13,10 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
{
public function testWithEmptyContent()
{
+ $tagger = $this->getTaggerMock();
+ $tagger->expects($this->once())
+ ->method('tag');
+
$graby = $this->getMockBuilder('Graby\Graby')
->setMethods(array('fetchContent'))
->disableOriginalConstructor()
@@ -25,7 +32,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
'language' => '',
));
- $proxy = new ContentProxy($graby);
+ $proxy = new ContentProxy($graby, $tagger, $this->getLogger());
$entry = $proxy->updateEntry(new Entry(new User()), 'http://0.0.0.0');
$this->assertEquals('http://0.0.0.0', $entry->getUrl());
@@ -40,6 +47,10 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
public function testWithEmptyContentButOG()
{
+ $tagger = $this->getTaggerMock();
+ $tagger->expects($this->once())
+ ->method('tag');
+
$graby = $this->getMockBuilder('Graby\Graby')
->setMethods(array('fetchContent'))
->disableOriginalConstructor()
@@ -59,7 +70,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
),
));
- $proxy = new ContentProxy($graby);
+ $proxy = new ContentProxy($graby, $tagger, $this->getLogger());
$entry = $proxy->updateEntry(new Entry(new User()), 'http://domain.io');
$this->assertEquals('http://domain.io', $entry->getUrl());
@@ -74,6 +85,10 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
public function testWithContent()
{
+ $tagger = $this->getTaggerMock();
+ $tagger->expects($this->once())
+ ->method('tag');
+
$graby = $this->getMockBuilder('Graby\Graby')
->setMethods(array('fetchContent'))
->disableOriginalConstructor()
@@ -94,7 +109,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
),
));
- $proxy = new ContentProxy($graby);
+ $proxy = new ContentProxy($graby, $tagger, $this->getLogger());
$entry = $proxy->updateEntry(new Entry(new User()), 'http://0.0.0.0');
$this->assertEquals('http://1.1.1.1', $entry->getUrl());
@@ -106,4 +121,17 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(4.0, $entry->getReadingTime());
$this->assertEquals('1.1.1.1', $entry->getDomainName());
}
+
+ private function getTaggerMock()
+ {
+ return $this->getMockBuilder('Wallabag\CoreBundle\Helper\RuleBasedTagger')
+ ->setMethods(array('tag'))
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getLogger()
+ {
+ return new NullLogger();
+ }
}
diff --git a/src/Wallabag/CoreBundle/Tests/Helper/RuleBasedTaggerTest.php b/src/Wallabag/CoreBundle/Tests/Helper/RuleBasedTaggerTest.php
new file mode 100644
index 000000000..5180f7ddb
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/Helper/RuleBasedTaggerTest.php
@@ -0,0 +1,167 @@
+rulerz = $this->getRulerZMock();
+ $this->tagRepository = $this->getTagRepositoryMock();
+ $this->entryRepository = $this->getEntryRepositoryMock();
+
+ $this->tagger = new RuleBasedTagger($this->rulerz, $this->tagRepository, $this->entryRepository);
+ }
+
+ public function testTagWithNoRule()
+ {
+ $entry = new Entry($this->getUser());
+
+ $this->tagger->tag($entry);
+
+ $this->assertTrue($entry->getTags()->isEmpty());
+ }
+
+ public function testTagWithNoMatchingRule()
+ {
+ $taggingRule = $this->getTaggingRule('rule as string', array('foo', 'bar'));
+ $user = $this->getUser([$taggingRule]);
+ $entry = new Entry($user);
+
+ $this->rulerz
+ ->expects($this->once())
+ ->method('satisfies')
+ ->with($entry, 'rule as string')
+ ->willReturn(false);
+
+ $this->tagger->tag($entry);
+
+ $this->assertTrue($entry->getTags()->isEmpty());
+ }
+
+ public function testTagWithAMatchingRule()
+ {
+ $taggingRule = $this->getTaggingRule('rule as string', array('foo', 'bar'));
+ $user = $this->getUser([$taggingRule]);
+ $entry = new Entry($user);
+
+ $this->rulerz
+ ->expects($this->once())
+ ->method('satisfies')
+ ->with($entry, 'rule as string')
+ ->willReturn(true);
+
+ $this->tagger->tag($entry);
+
+ $this->assertFalse($entry->getTags()->isEmpty());
+
+ $tags = $entry->getTags();
+ $this->assertSame('foo', $tags[0]->getLabel());
+ $this->assertSame($user, $tags[0]->getUser());
+ $this->assertSame('bar', $tags[1]->getLabel());
+ $this->assertSame($user, $tags[1]->getUser());
+ }
+
+ public function testTagWithAMixOfMatchingRules()
+ {
+ $taggingRule = $this->getTaggingRule('bla bla', array('hey'));
+ $otherTaggingRule = $this->getTaggingRule('rule as string', array('foo'));
+
+ $user = $this->getUser([$taggingRule, $otherTaggingRule]);
+ $entry = new Entry($user);
+
+ $this->rulerz
+ ->method('satisfies')
+ ->will($this->onConsecutiveCalls(false, true));
+
+ $this->tagger->tag($entry);
+
+ $this->assertFalse($entry->getTags()->isEmpty());
+
+ $tags = $entry->getTags();
+ $this->assertSame('foo', $tags[0]->getLabel());
+ $this->assertSame($user, $tags[0]->getUser());
+ }
+
+ public function testWhenTheTagExists()
+ {
+ $taggingRule = $this->getTaggingRule('rule as string', array('foo'));
+ $user = $this->getUser([$taggingRule]);
+ $entry = new Entry($user);
+ $tag = new Tag($user);
+
+ $this->rulerz
+ ->expects($this->once())
+ ->method('satisfies')
+ ->with($entry, 'rule as string')
+ ->willReturn(true);
+
+ $this->tagRepository
+ ->expects($this->once())
+ ->method('findOneByLabelAndUserId')
+ ->willReturn($tag);
+
+ $this->tagger->tag($entry);
+
+ $this->assertFalse($entry->getTags()->isEmpty());
+
+ $tags = $entry->getTags();
+ $this->assertSame($tag, $tags[0]);
+ }
+
+ private function getUser(array $taggingRules = [])
+ {
+ $user = new User();
+ $config = new Config($user);
+
+ $user->setConfig($config);
+
+ foreach ($taggingRules as $rule) {
+ $config->addTaggingRule($rule);
+ }
+
+ return $user;
+ }
+
+ private function getTaggingRule($rule, array $tags)
+ {
+ $taggingRule = new TaggingRule();
+ $taggingRule->setRule($rule);
+ $taggingRule->setTags($tags);
+
+ return $taggingRule;
+ }
+
+ private function getRulerZMock()
+ {
+ return $this->getMockBuilder('RulerZ\RulerZ')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getTagRepositoryMock()
+ {
+ return $this->getMockBuilder('Wallabag\CoreBundle\Repository\TagRepository')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getEntryRepositoryMock()
+ {
+ return $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+}
diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php
index c020f3ca9..009c4881d 100644
--- a/src/Wallabag/UserBundle/Repository/UserRepository.php
+++ b/src/Wallabag/UserBundle/Repository/UserRepository.php
@@ -23,4 +23,19 @@ class UserRepository extends EntityRepository
->getQuery()
->getOneOrNullResult();
}
+
+ /**
+ * Find a user by its username.
+ *
+ * @param string $username
+ *
+ * @return User
+ */
+ public function findOneByUserName($username)
+ {
+ return $this->createQueryBuilder('u')
+ ->andWhere('u.username = :username')->setParameter('username', $username)
+ ->getQuery()
+ ->getSingleResult();
+ }
}