Added notmatches operator for tagging rule

This commit is contained in:
Nicolas Lœuillet 2017-04-20 14:58:20 +02:00
parent 64f1d8f77a
commit fdd725f58c
32 changed files with 125 additions and 48 deletions

View file

@ -115,16 +115,17 @@ Welche Variablen und Operatoren kann ich zum Regeln schreiben nutzen?
Die folgenden Variablen und Operatoren können genutzt werden, um Tagging-Regeln zu erstellen (sei vorsichtig, denn bei einigen Werten musst du Anführungszeichen hinzufügen, z.B. ``language = "de"``):
=========== ============================================== ======== ==========
Variable Bedeutung Operator Bedeutung
----------- ---------------------------------------------- -------- ----------
title Titel des Artikels <= Kleiner gleich als…
url URL des Artikels < Kleiner als…
isArchived Ob der Artikel archiviert ist oder nicht => Größer gleich als…
isStarred Ob der Artikel favorisiert ist oder nicht > Größer als…
content Inhalt des Eintrags = Gleich zu…
language Sprache des Eintrags != Nicht gleich zu…
mimetype MIME-Typ des Eintrags OR Eine Regel oder die andere
readingTime Die geschätzte Lesezeit in Minuten AND Eine Regel und die andere
domainName Der Domain-Name des Eintrags matches Testet, dass ein Feld einer Suche (unabhängig von Groß- und Kleinschreibung) übereinstimmt. Z.B.: title matches "Fußball"
=========== ============================================== ======== ==========
=========== ============================================== ========== ==========
Variable Bedeutung Operator Bedeutung
----------- ---------------------------------------------- ---------- ----------
title Titel des Artikels <= Kleiner gleich als…
url URL des Artikels < Kleiner als…
isArchived Ob der Artikel archiviert ist oder nicht => Größer gleich als…
isStarred Ob der Artikel favorisiert ist oder nicht > Größer als…
content Inhalt des Eintrags = Gleich zu…
language Sprache des Eintrags != Nicht gleich zu…
mimetype MIME-Typ des Eintrags OR Eine Regel oder die andere
readingTime Die geschätzte Lesezeit in Minuten AND Eine Regel und die andere
domainName Der Domain-Name des Eintrags matches Testet, dass ein Feld einer Suche (unabhängig von Groß- und Kleinschreibung) übereinstimmt. Z.B.: title matches "Fußball"
notmatches
=========== ============================================== ========== ==========

View file

@ -116,16 +116,17 @@ Which variables and operators can I use to write rules?
The following variables and operators can be used to create tagging rules (be careful, for some values, you need to add quotes, for example ``language = "en"``):
=========== ============================================== ======== ==========
Variable Meaning Operator Meaning
----------- ---------------------------------------------- -------- ----------
title Title of the entry <= Less than…
url URL of the entry < Strictly less than…
isArchived Whether the entry is archived or not => Greater than…
isStarred Whether the entry is starred or not > Strictly greater than…
content The entry's content = Equal to…
language The entry's language != Not equal to…
mimetype The entry's mime-type OR One rule or another
readingTime The estimated entry's reading time, in minutes AND One rule and another
domainName The domain name of the entry matches Tests that a subject is matches a search (case-insensitive). Example: title matches "football"
=========== ============================================== ======== ==========
=========== ============================================== ========== ==========
Variable Meaning Operator Meaning
----------- ---------------------------------------------- ---------- ----------
title Title of the entry <= Less than…
url URL of the entry < Strictly less than…
isArchived Whether the entry is archived or not => Greater than…
isStarred Whether the entry is starred or not > Strictly greater than…
content The entry's content = Equal to…
language The entry's language != Not equal to…
mimetype The entry's mime-type OR One rule or another
readingTime The estimated entry's reading time, in minutes AND One rule and another
domainName The domain name of the entry matches Tests that a subject is matches a search (case-insensitive). Example: title matches "football"
notmatches Tests that a subject is not matches a search (case-insensitive). Example: title notmatches "football"
=========== ============================================== ========== ==========

View file

@ -130,4 +130,5 @@ language La langue de l'article != Différ
mimetype The type MIME de l'article OR Telle règle ou telle autre règle
readingTime Le temps de lecture de l'article, en minutes AND Telle règle et telle règle
domainName Le nom de domaine de l'article matches Contient telle chaîne de caractère (insensible à la casse). Exemple : title matches "football"
notmaches Ne contient pas telle chaîne de caractère (insensible à la casse). Exemple : title notmatches "football"
=========== ============================================== ========== ==========

View file

@ -36,6 +36,13 @@ class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInter
$manager->persist($tr3);
$tr4 = new TaggingRule();
$tr4->setRule('content notmatches "basket"');
$tr4->setTags(['foot']);
$tr4->setConfig($this->getReference('admin-config'));
$manager->persist($tr4);
$manager->flush();
}

View file

@ -31,7 +31,7 @@ class TaggingRule
* @Assert\Length(max=255)
* @RulerZAssert\ValidRule(
* allowed_variables={"title", "url", "isArchived", "isStared", "content", "language", "mimetype", "readingTime", "domainName"},
* allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches"}
* allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches", "notmatches"}
* )
* @ORM\Column(name="rule", type="string", nullable=false)
*/
@ -87,7 +87,7 @@ class TaggingRule
/**
* Set tags.
*
* @param array<string> $tags
* @param array <string> $tags
*
* @return TaggingRule
*/

View file

@ -0,0 +1,25 @@
<?php
namespace Wallabag\CoreBundle\Operator\Doctrine;
/**
* Provides a "notmatches" operator used for tagging rules.
*
* It asserts that a given pattern is not contained in a subject, in a
* case-insensitive way.
*
* This operator will be used to compile tagging rules in DQL, usable
* by Doctrine ORM.
* It's registered in RulerZ using a service (wallabag.operator.doctrine.matches);
*/
class NotMatches
{
public function __invoke($subject, $pattern)
{
if ($pattern[0] === "'") {
$pattern = sprintf("'%%%s%%'", substr($pattern, 1, -1));
}
return sprintf('UPPER(%s) NOT LIKE UPPER(%s)', $subject, $pattern);
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Wallabag\CoreBundle\Operator\PHP;
/**
* Provides a "notmatches" operator used for tagging rules.
*
* It asserts that a given pattern is not contained in a subject, in a
* case-insensitive way.
*
* This operator will be used to compile tagging rules in PHP, usable
* directly on Entry objects for instance.
* It's registered in RulerZ using a service (wallabag.operator.array.matches);
*/
class NotMatches
{
public function __invoke($subject, $pattern)
{
return stripos($subject, $pattern) === false;
}
}

View file

@ -125,6 +125,16 @@ services:
tags:
- { name: rulerz.operator, target: doctrine, operator: matches, inline: true }
wallabag.operator.array.notmatches:
class: Wallabag\CoreBundle\Operator\PHP\NotMatches
tags:
- { name: rulerz.operator, target: native, operator: notmatches }
wallabag.operator.doctrine.notmatches:
class: Wallabag\CoreBundle\Operator\Doctrine\NotMatches
tags:
- { name: rulerz.operator, target: doctrine, operator: notmatches, inline: true }
wallabag_core.helper.redirect:
class: Wallabag\CoreBundle\Helper\Redirect
arguments:

View file

@ -155,7 +155,7 @@ config:
# or: 'One rule OR another'
# and: 'One rule AND another'
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:
# unread: 'Unread entries'

View file

@ -155,6 +155,7 @@ config:
or: 'Eine Regel ODER die andere'
and: 'Eine Regel UND eine andere'
matches: 'Testet, ob eine <i>Variable</i> auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title matches "Fußball"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: 'One rule OR another'
and: 'One rule AND another'
matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: 'Una regla U otra'
and: 'Una regla Y la otra'
matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
# or: 'One rule OR another'
# and: 'One rule AND another'
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: "Une règle OU lautre"
and: "Une règle ET lautre"
matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>"
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: "Una regola O un'altra"
and: "Una regola E un'altra"
matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: "Una règla O l'autra"
and: "Una règla E l'autra"
matches: 'Teste se un <i>subjècte</i> correspond a una <i>recerca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: 'Jedna reguła LUB inna'
and: 'Jedna reguła I inna'
matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: 'Uma regra OU outra'
and: 'Uma regra E outra'
matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
# or: 'One rule OR another'
# and: 'One rule AND another'
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -155,6 +155,7 @@ config:
or: 'Bir kural veya birbaşkası'
and: 'Bir kural ve diğeri'
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
entry:
page_titles:

View file

@ -413,8 +413,8 @@
<tr>
<td>domainName</td>
<td>{{ 'config.form_rules.faq.variable_description.domainName'|trans }}</td>
<td>matches</td>
<td>{{ 'config.form_rules.faq.operator_description.matches'|trans|raw }}</td>
<td>matches<br />notmaches</td>
<td>{{ 'config.form_rules.faq.operator_description.matches'|trans|raw }}<br />{{ 'config.form_rules.faq.operator_description.notmatches'|trans|raw }}</td>
</tr>
</tbody>
</table>

View file

@ -337,7 +337,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
$this->assertEquals(false, $content['is_starred']);
$this->assertEquals('New title for my article', $content['title']);
$this->assertEquals(1, $content['user_id']);
$this->assertCount(1, $content['tags']);
$this->assertCount(2, $content['tags']);
}
public function testPostSameEntry()
@ -356,7 +356,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
$this->assertEquals('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', $content['url']);
$this->assertEquals(true, $content['is_archived']);
$this->assertEquals(false, $content['is_starred']);
$this->assertCount(2, $content['tags']);
$this->assertCount(3, $content['tags']);
}
public function testPostArchivedAndStarredEntry()

View file

@ -270,7 +270,7 @@ class EntryControllerTest extends WallabagCoreTestCase
->findOneByUrl($url);
$tags = $entry->getTags();
$this->assertCount(1, $tags);
$this->assertCount(2, $tags);
$this->assertEquals('wallabag', $tags[0]->getLabel());
$em->remove($entry);
@ -299,8 +299,8 @@ class EntryControllerTest extends WallabagCoreTestCase
$tags = $entry->getTags();
$this->assertCount(1, $tags);
$this->assertEquals('wallabag', $tags[0]->getLabel());
$this->assertCount(2, $tags);
$this->assertEquals('wallabag', $tags[1]->getLabel());
$em->remove($entry);
$em->flush();

View file

@ -241,7 +241,7 @@ class ExportControllerTest extends WallabagCoreTestCase
$this->assertEquals($contentInDB->getLanguage(), $content[0]['language']);
$this->assertEquals($contentInDB->getReadingtime(), $content[0]['reading_time']);
$this->assertEquals($contentInDB->getDomainname(), $content[0]['domain_name']);
$this->assertEquals(['foo bar', 'baz'], $content[0]['tags']);
$this->assertEquals(['foo bar', 'baz', 'foot'], $content[0]['tags']);
}
public function testXmlExport()

View file

@ -46,7 +46,7 @@ class TagControllerTest extends WallabagCoreTestCase
->getRepository('WallabagCoreBundle:Entry')
->findByUrlAndUserId('http://0.0.0.0/entry1', $this->getLoggedInUserId());
$this->assertEquals(3, count($entry->getTags()));
$this->assertEquals(4, count($entry->getTags()));
// tag already exists and already assigned
$client->submit($form, $data);
@ -57,7 +57,7 @@ class TagControllerTest extends WallabagCoreTestCase
->getRepository('WallabagCoreBundle:Entry')
->find($entry->getId());
$this->assertEquals(3, count($newEntry->getTags()));
$this->assertEquals(4, count($newEntry->getTags()));
// tag already exists but still not assigned to this entry
$data = [
@ -72,7 +72,7 @@ class TagControllerTest extends WallabagCoreTestCase
->getRepository('WallabagCoreBundle:Entry')
->find($entry->getId());
$this->assertEquals(3, count($newEntry->getTags()));
$this->assertEquals(4, count($newEntry->getTags()));
}
public function testAddMultipleTagToEntry()

View file

@ -120,7 +120,7 @@ class ChromeControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.usinenouvelle.com is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for http://www.usinenouvelle.com is ok');
$this->assertEquals(0, count($content->getTags()));
$this->assertEquals(1, count($content->getTags()));
$createdAt = $content->getCreatedAt();
$this->assertEquals('2011', $createdAt->format('Y'));

View file

@ -121,7 +121,7 @@ class FirefoxControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://lexpansion.lexpress.fr is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://lexpansion.lexpress.fr is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for http://lexpansion.lexpress.fr is ok');
$this->assertEquals(2, count($content->getTags()));
$this->assertEquals(3, count($content->getTags()));
$content = $client->getContainer()
->get('doctrine.orm.entity_manager')

View file

@ -121,7 +121,7 @@ class InstapaperControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.liberation.fr is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.liberation.fr is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for http://www.liberation.fr is ok');
$this->assertEquals(0, count($content->getTags()));
$this->assertEquals(1, count($content->getTags()));
$this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
}

View file

@ -121,7 +121,7 @@ class PinboardControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for https://ma.ttias.be is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for https://ma.ttias.be is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for https://ma.ttias.be is ok');
$this->assertEquals(2, count($content->getTags()));
$this->assertEquals(3, count($content->getTags()));
$this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
$this->assertEquals('2016-10-26', $content->getCreatedAt()->format('Y-m-d'));
}

View file

@ -121,7 +121,7 @@ class ReadabilityControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.zataz.com is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.zataz.com is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for http://www.zataz.com is ok');
$this->assertEquals(0, count($content->getTags()));
$this->assertEquals(1, count($content->getTags()));
$this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
$this->assertEquals('2016-09-08', $content->getCreatedAt()->format('Y-m-d'));
}

View file

@ -129,7 +129,7 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.framablog.org is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.framablog.org is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for http://www.framablog.org is ok');
$this->assertEquals(1, count($content->getTags()));
$this->assertEquals(2, count($content->getTags()));
$this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
}

View file

@ -122,7 +122,7 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.liberation.fr is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.liberation.fr is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for http://www.liberation.fr is ok');
$this->assertEquals(0, count($content->getTags()));
$this->assertEquals(1, count($content->getTags()));
$content = $client->getContainer()
->get('doctrine.orm.entity_manager')
@ -135,7 +135,7 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty($content->getMimetype(), 'Mimetype for https://www.mediapart.fr is ok');
$this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for https://www.mediapart.fr is ok');
$this->assertNotEmpty($content->getLanguage(), 'Language for https://www.mediapart.fr is ok');
$this->assertEquals(2, count($content->getTags()));
$this->assertEquals(3, count($content->getTags()));
$this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
$this->assertEquals('2016-09-08', $content->getCreatedAt()->format('Y-m-d'));
}