new FOS\OAuthServerBundle\FOSOAuthServerBundle(),
new Wallabag\UserBundle\WallabagUserBundle(),
new Scheb\TwoFactorBundle\SchebTwoFactorBundle(),
+ new KPhoen\RulerZBundle\KPhoenRulerZBundle(),
);
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
sender_email: %twofactor_sender%
digits: 6
template: WallabagUserBundle:Authentication:form.html.twig
+
+kphoen_rulerz:
+ executors:
+ doctrine: true
"friendsofsymfony/oauth-server-bundle": "^1.4@dev",
"scheb/two-factor-bundle": "~1.4",
"grandt/phpepub": "~4.0",
- "wallabag/php-mobi": "~1.0.0"
+ "wallabag/php-mobi": "~1.0.0",
+ "kphoen/rulerz-bundle": "~0.10"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2.0",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "a9ec461e17166dcda1563dd55f6ff861",
+ "hash": "fed9468f6c830b0f81899daad7670af7",
+ "content-hash": "394f8a6ca5162f2d2756dbbee0ff5aae",
"packages": [
{
"name": "doctrine/annotations",
],
"time": "2014-10-12 19:18:40"
},
+ {
+ "name": "hoa/compiler",
+ "version": "2.15.10.29",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Compiler.git",
+ "reference": "ec0849fd3c1472fbcd86c3c961981f0cfe1f8d39"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Compiler/zipball/ec0849fd3c1472fbcd86c3c961981f0cfe1f8d39",
+ "reference": "ec0849fd3c1472fbcd86c3c961981f0cfe1f8d39",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0",
+ "hoa/file": "~0.0",
+ "hoa/iterator": "~1.0",
+ "hoa/math": "~0.0",
+ "hoa/regex": "~0.0",
+ "hoa/visitor": "~1.0"
+ },
+ "require-dev": {
+ "hoa/json": "~1.0",
+ "hoa/test": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Compiler\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Compiler library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "algebraic",
+ "ast",
+ "compiler",
+ "context-free",
+ "coverage",
+ "exhaustive",
+ "grammar",
+ "isotropic",
+ "language",
+ "lexer",
+ "library",
+ "ll1",
+ "llk",
+ "parser",
+ "pp",
+ "random",
+ "regular",
+ "rule",
+ "sampler",
+ "syntax",
+ "token",
+ "trace",
+ "uniform"
+ ],
+ "time": "2015-10-29 21:35:12"
+ },
+ {
+ "name": "hoa/core",
+ "version": "2.15.11.09",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Core.git",
+ "reference": "5538b1e90e2c66c90df5cc45e03fb85d047be900"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Core/zipball/5538b1e90e2c66c90df5cc45e03fb85d047be900",
+ "reference": "5538b1e90e2c66c90df5cc45e03fb85d047be900",
+ "shasum": ""
+ },
+ "require": {
+ "ext-spl": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "suggest": {
+ "ext-mbstring": "ext/mbstring must be present (or a third implementation).",
+ "hoa/cli": "To use the `hoa` script."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Core\\": "."
+ },
+ "files": [
+ "Core.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Core library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "consistency",
+ "core",
+ "data",
+ "event",
+ "library",
+ "listener",
+ "parameter",
+ "protocol"
+ ],
+ "time": "2015-11-09 06:51:06"
+ },
+ {
+ "name": "hoa/file",
+ "version": "0.15.11.09",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/File.git",
+ "reference": "f46fe552ff79cb6c93a2ff9c25cfbc134fbd57ee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/File/zipball/f46fe552ff79cb6c93a2ff9c25cfbc134fbd57ee",
+ "reference": "f46fe552ff79cb6c93a2ff9c25cfbc134fbd57ee",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0",
+ "hoa/iterator": "~1.0",
+ "hoa/stream": "~0.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\File\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\File library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "Socket",
+ "directory",
+ "file",
+ "finder",
+ "library",
+ "link",
+ "temporary"
+ ],
+ "time": "2015-11-09 06:55:20"
+ },
+ {
+ "name": "hoa/iterator",
+ "version": "1.15.10.29",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Iterator.git",
+ "reference": "a64ed9fd62579a34e4450134d6d1abdf77d54435"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/a64ed9fd62579a34e4450134d6d1abdf77d54435",
+ "reference": "a64ed9fd62579a34e4450134d6d1abdf77d54435",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Iterator\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Iterator library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "iterator",
+ "library"
+ ],
+ "time": "2015-10-29 21:37:16"
+ },
+ {
+ "name": "hoa/math",
+ "version": "0.15.10.26",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Math.git",
+ "reference": "62631c65d9a4f1b8bb4c4a3d6cdff0e8971d684e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Math/zipball/62631c65d9a4f1b8bb4c4a3d6cdff0e8971d684e",
+ "reference": "62631c65d9a4f1b8bb4c4a3d6cdff0e8971d684e",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/compiler": "~2.0",
+ "hoa/core": "~2.0",
+ "hoa/iterator": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Math\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Math library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "arrangement",
+ "combination",
+ "combinatorics",
+ "counting",
+ "library",
+ "math",
+ "permutation",
+ "sampler",
+ "set"
+ ],
+ "time": "2015-10-26 15:22:52"
+ },
+ {
+ "name": "hoa/regex",
+ "version": "0.15.08.13",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Regex.git",
+ "reference": "2ef8a77ef3885ca202fcd9c31a8e54c44cd04232"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Regex/zipball/2ef8a77ef3885ca202fcd9c31a8e54c44cd04232",
+ "reference": "2ef8a77ef3885ca202fcd9c31a8e54c44cd04232",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0",
+ "hoa/math": "~0.0",
+ "hoa/ustring": "~3.0",
+ "hoa/visitor": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Regex\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Regex library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "compiler",
+ "library",
+ "regex"
+ ],
+ "time": "2015-08-13 06:48:47"
+ },
+ {
+ "name": "hoa/ruler",
+ "version": "1.15.11.09",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Ruler.git",
+ "reference": "9afc9ae032d40b6dc10bff85c9126cf516953925"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Ruler/zipball/9afc9ae032d40b6dc10bff85c9126cf516953925",
+ "reference": "9afc9ae032d40b6dc10bff85c9126cf516953925",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/compiler": "~2.0",
+ "hoa/core": "~2.0",
+ "hoa/file": "~0.0",
+ "hoa/visitor": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Ruler\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Ruler library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "library",
+ "ruler"
+ ],
+ "time": "2015-11-09 06:58:52"
+ },
+ {
+ "name": "hoa/stream",
+ "version": "0.15.10.26",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Stream.git",
+ "reference": "011ab91d942f1d7096deade4c8a10fe57d51c5b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Stream/zipball/011ab91d942f1d7096deade4c8a10fe57d51c5b3",
+ "reference": "011ab91d942f1d7096deade4c8a10fe57d51c5b3",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Stream\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Stream library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "Context",
+ "bucket",
+ "composite",
+ "filter",
+ "in",
+ "library",
+ "out",
+ "protocol",
+ "stream",
+ "wrapper"
+ ],
+ "time": "2015-10-22 06:30:43"
+ },
+ {
+ "name": "hoa/ustring",
+ "version": "3.15.11.09",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Ustring.git",
+ "reference": "8506be4910212b1a2beb9014763a8a4fbd871001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Ustring/zipball/8506be4910212b1a2beb9014763a8a4fbd871001",
+ "reference": "8506be4910212b1a2beb9014763a8a4fbd871001",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "suggest": {
+ "ext-iconv": "ext/iconv must be present (or a third implementation) to use Hoa\\Ustring::transcode().",
+ "ext-intl": "To get a better Hoa\\Ustring::toAscii() and Hoa\\Ustring::compareTo()."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Ustring\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Ustring library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "library",
+ "search",
+ "string",
+ "unicode"
+ ],
+ "time": "2015-11-09 06:44:33"
+ },
+ {
+ "name": "hoa/visitor",
+ "version": "1.15.08.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Visitor.git",
+ "reference": "e30bfff741f71979f6476a41548e34afe8053c67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Visitor/zipball/e30bfff741f71979f6476a41548e34afe8053c67",
+ "reference": "e30bfff741f71979f6476a41548e34afe8053c67",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/core": "~2.0"
+ },
+ "require-dev": {
+ "hoa/test": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Visitor\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "http://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Visitor library.",
+ "homepage": "http://hoa-project.net/",
+ "keywords": [
+ "library",
+ "structure",
+ "visit",
+ "visitor"
+ ],
+ "time": "2015-08-17 06:30:58"
+ },
{
"name": "htmlawed/htmlawed",
"version": "1.1.19",
},
{
"name": "incenteev/composer-parameter-handler",
- "version": "v2.1.1",
+ "version": "v2.1.2",
"source": {
"type": "git",
"url": "https://github.com/Incenteev/ParameterHandler.git",
- "reference": "84a205fe80a46101607bafbc423019527893ddd0"
+ "reference": "d7ce7f06136109e81d1cb9d57066c4d4a99cf1cc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Incenteev/ParameterHandler/zipball/84a205fe80a46101607bafbc423019527893ddd0",
- "reference": "84a205fe80a46101607bafbc423019527893ddd0",
+ "url": "https://api.github.com/repos/Incenteev/ParameterHandler/zipball/d7ce7f06136109e81d1cb9d57066c4d4a99cf1cc",
+ "reference": "d7ce7f06136109e81d1cb9d57066c4d4a99cf1cc",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
- "symfony/yaml": "~2.0"
+ "symfony/yaml": "~2.3|~3.0"
},
"require-dev": {
"composer/composer": "1.0.*@dev",
"keywords": [
"parameters management"
],
- "time": "2015-06-03 08:27:03"
+ "time": "2015-11-10 17:04:01"
},
{
"name": "j0k3r/graby",
},
{
"name": "j0k3r/php-readability",
- "version": "v1.0.8",
+ "version": "v1.0.9",
"source": {
"type": "git",
"url": "https://github.com/j0k3r/php-readability.git",
- "reference": "f71c3a419623f821c245e0a003edfbf2c67f278e"
+ "reference": "41d7440c6e6130bacd50808342fe566e28f536fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/j0k3r/php-readability/zipball/f71c3a419623f821c245e0a003edfbf2c67f278e",
- "reference": "f71c3a419623f821c245e0a003edfbf2c67f278e",
+ "url": "https://api.github.com/repos/j0k3r/php-readability/zipball/41d7440c6e6130bacd50808342fe566e28f536fb",
+ "reference": "41d7440c6e6130bacd50808342fe566e28f536fb",
"shasum": ""
},
"require": {
- "ext-tidy": ">=1.2",
"php": ">=5.3.3"
},
"type": "library",
"extraction",
"html"
],
- "time": "2015-09-23 19:09:38"
+ "time": "2015-11-10 08:55:29"
},
{
"name": "j0k3r/safecurl",
],
"time": "2013-12-05 14:36:11"
},
+ {
+ "name": "kphoen/rulerz",
+ "version": "0.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/K-Phoen/rulerz.git",
+ "reference": "608649b148ffdf3437600cc0f450d59b0579148d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/K-Phoen/rulerz/zipball/608649b148ffdf3437600cc0f450d59b0579148d",
+ "reference": "608649b148ffdf3437600cc0f450d59b0579148d",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/ruler": "~1.0",
+ "php": ">=5.4",
+ "symfony/property-access": "~2.3"
+ },
+ "require-dev": {
+ "behat/behat": "~3.0",
+ "coduo/phpspec-data-provider-extension": "~1.0,!=1.0.2",
+ "doctrine/orm": "~2.4",
+ "elasticsearch/elasticsearch": "~1.0",
+ "illuminate/database": "~5.0",
+ "mikey179/vfsstream": "~1.4",
+ "phpspec/phpspec": "~2.0",
+ "pomm-project/cli": "~2.0@dev",
+ "pomm-project/foundation": "~2.0@dev",
+ "pomm-project/model-manager": "~2.0.@dev",
+ "ruflin/elastica": "~1.0",
+ "vlucas/phpdotenv": "~2.1"
+ },
+ "suggest": {
+ "doctrine/orm": "To execute rules as Doctrine queries",
+ "elasticsearch/elasticsearch": "To execute rules as Elasticsearch queries",
+ "kphoen/rulerz-spec-builder": "If you want a specification builder",
+ "pomm-project/model-manager": "To execute rules as Pomm queries"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "RulerZ\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kévin Gomez",
+ "email": "contact@kevingomez.fr"
+ }
+ ],
+ "description": "Powerful implementation of the Specification pattern",
+ "homepage": "https://github.com/K-Phoen/RulerZ",
+ "keywords": [
+ "doctrine",
+ "specification"
+ ],
+ "time": "2015-10-31 20:54:37"
+ },
+ {
+ "name": "kphoen/rulerz-bundle",
+ "version": "0.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/K-Phoen/RulerZBundle.git",
+ "reference": "dcaaed69d8252fa1e3a25802f8cf697947570778"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/K-Phoen/RulerZBundle/zipball/dcaaed69d8252fa1e3a25802f8cf697947570778",
+ "reference": "dcaaed69d8252fa1e3a25802f8cf697947570778",
+ "shasum": ""
+ },
+ "require": {
+ "kphoen/rulerz": "~0.1, >=0.13.0",
+ "symfony/framework-bundle": "~2.3|~3.0",
+ "symfony/validator": "~2.3|~3.0"
+ },
+ "require-dev": {
+ "matthiasnoback/symfony-dependency-injection-test": "~0.7",
+ "mikey179/vfsstream": "~1.0",
+ "phpunit/phpunit": "~4.8"
+ },
+ "type": "symfony-bundle",
+ "autoload": {
+ "psr-4": {
+ "KPhoen\\RulerZBundle\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kévin Gomez",
+ "email": "contact@kevingomez.fr"
+ }
+ ],
+ "description": "Symfony2 Bundle for RulerZ",
+ "homepage": "https://github.com/K-Phoen/RulerZBundle",
+ "keywords": [
+ "doctrine",
+ "ruler",
+ "rulerz",
+ "specification"
+ ],
+ "time": "2015-11-13 13:00:14"
+ },
{
"name": "kriswallsmith/assetic",
"version": "v1.3.1",
},
{
"name": "sensio/framework-extra-bundle",
- "version": "v3.0.10",
+ "version": "v3.0.11",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
- "reference": "18fc2063c4d6569cdca47a39fbac32342eb65f3c"
+ "reference": "a79e205737b58d557c05caef6dfa8f94d8084bca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/18fc2063c4d6569cdca47a39fbac32342eb65f3c",
- "reference": "18fc2063c4d6569cdca47a39fbac32342eb65f3c",
+ "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/a79e205737b58d557c05caef6dfa8f94d8084bca",
+ "reference": "a79e205737b58d557c05caef6dfa8f94d8084bca",
"shasum": ""
},
"require": {
"doctrine/common": "~2.2",
- "symfony/framework-bundle": "~2.3"
+ "symfony/framework-bundle": "~2.3|~3.0"
},
"require-dev": {
- "symfony/expression-language": "~2.4",
- "symfony/security-bundle": "~2.4"
+ "symfony/expression-language": "~2.4|~3.0",
+ "symfony/security-bundle": "~2.4|~3.0"
},
"suggest": {
"symfony/expression-language": "",
"annotations",
"controllers"
],
- "time": "2015-08-03 11:59:27"
+ "time": "2015-10-28 15:47:04"
},
{
"name": "sensiolabs/security-checker",
},
{
"name": "phpunit/phpunit",
- "version": "4.8.16",
+ "version": "4.8.18",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e"
+ "reference": "fa33d4ad96481b91df343d83e8c8aabed6b1dfd3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/625f8c345606ed0f3a141dfb88f4116f0e22978e",
- "reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fa33d4ad96481b91df343d83e8c8aabed6b1dfd3",
+ "reference": "fa33d4ad96481b91df343d83e8c8aabed6b1dfd3",
"shasum": ""
},
"require": {
"testing",
"xunit"
],
- "time": "2015-10-23 06:48:33"
+ "time": "2015-11-11 11:32:49"
},
{
"name": "phpunit/phpunit-mock-objects",
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Command;
+
+use Doctrine\ORM\NoResultException;
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class TagAllCommand extends ContainerAwareCommand
+{
+ protected function configure()
+ {
+ $this
+ ->setName('wallabag:tag:all')
+ ->setDescription('Tag all entries using the tagging rules.')
+ ->addArgument(
+ 'username',
+ InputArgument::REQUIRED,
+ 'User to tag entries for.'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ try {
+ $user = $this->getUser($input->getArgument('username'));
+ } catch (NoResultException $e) {
+ $output->writeln(sprintf('<error>User %s not found.</error>', $input->getArgument('username')));
+
+ return 1;
+ }
+ $tagger = $this->getContainer()->get('wallabag_core.rule_based_tagger');
+
+ $output->write(sprintf('Tagging entries for user « <info>%s</info> »... ', $user->getUserName()));
+
+ $entries = $tagger->tagAllForUser($user);
+
+ $em = $this->getDoctrine()->getManager();
+ foreach ($entries as $entry) {
+ $em->persist($entry);
+ }
+ $em->flush();
+
+ $output->writeln('<info>Done.</info>');
+ }
+
+ /**
+ * Fetches a user from its username.
+ *
+ * @param string $username
+ *
+ * @return \Wallabag\UserBundle\Entity\User
+ */
+ private function getUser($username)
+ {
+ return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
+ }
+
+ private function getDoctrine()
+ {
+ return $this->getContainer()->get('doctrine');
+ }
+}
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Wallabag\CoreBundle\Entity\Config;
+use Wallabag\CoreBundle\Entity\TaggingRule;
use Wallabag\UserBundle\Entity\User;
use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
use Wallabag\CoreBundle\Form\Type\UserInformationType;
+use Wallabag\CoreBundle\Form\Type\TaggingRuleType;
use Wallabag\CoreBundle\Form\Type\NewUserType;
use Wallabag\CoreBundle\Form\Type\RssType;
use Wallabag\CoreBundle\Tools\Utils;
return $this->redirect($this->generateUrl('config'));
}
+ // handle tagging rule
+ $taggingRule = new TaggingRule();
+ $newTaggingRule = $this->createForm(new TaggingRuleType(), $taggingRule, array('action' => $this->generateUrl('config').'#set5'));
+ $newTaggingRule->handleRequest($request);
+
+ if ($newTaggingRule->isValid()) {
+ $taggingRule->setConfig($config);
+ $em->persist($taggingRule);
+ $em->flush();
+
+ $this->get('session')->getFlashBag()->add(
+ 'notice',
+ 'Tagging rules updated'
+ );
+
+ return $this->redirect($this->generateUrl('config'));
+ }
+
// handle adding new user
$newUser = $userManager->createUser();
// enable created user by default
'pwd' => $pwdForm->createView(),
'user' => $userForm->createView(),
'new_user' => $newUserForm->createView(),
+ 'new_tagging_rule' => $newTaggingRule->createView(),
),
'rss' => array(
'username' => $user->getUsername(),
return $request->headers->get('referer') ? $this->redirect($request->headers->get('referer')) : $this->redirectToRoute('config');
}
+ /**
+ * Deletes a tagging rule and redirect to the config homepage.
+ *
+ * @param TaggingRule $rule
+ *
+ * @Route("/tagging-rule/delete/{id}", requirements={"id" = "\d+"}, name="delete_tagging_rule")
+ *
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ */
+ public function deleteTaggingRule(TaggingRule $rule)
+ {
+ if ($this->getUser()->getId() != $rule->getConfig()->getUser()->getId()) {
+ throw $this->createAccessDeniedException('You can not access this tagging ryle.');
+ }
+
+ $em = $this->getDoctrine()->getManager();
+ $em->remove($rule);
+ $em->flush();
+
+ $this->get('session')->getFlashBag()->add(
+ 'notice',
+ 'Tagging rule deleted'
+ );
+
+ return $this->redirect($this->generateUrl('config'));
+ }
+
/**
* Retrieve config for the current user.
* If no config were found, create a new one.
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\Config;
+use Wallabag\CoreBundle\Entity\TaggingRule;
class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
$adminConfig = new Config($this->getReference('admin-user'));
+ $taggingRule = new TaggingRule();
+
+ $taggingRule->setConfig($adminConfig);
+ $taggingRule->setRule('title matches "wallabag"');
+ $taggingRule->setTags(['wallabag']);
+ $manager->persist($taggingRule);
+
$adminConfig->setTheme('material');
$adminConfig->setItemsPerPage(30);
$adminConfig->setLanguage('en_US');
namespace Wallabag\CoreBundle\Entity;
+use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
*/
private $user;
+ /**
+ * @ORM\OneToMany(targetEntity="Wallabag\CoreBundle\Entity\TaggingRule", mappedBy="config", cascade={"remove"})
+ * @ORM\OrderBy({"id" = "ASC"})
+ */
+ private $taggingRules;
+
/*
* @param User $user
*/
public function __construct(\Wallabag\UserBundle\Entity\User $user)
{
$this->user = $user;
+ $this->taggingRules = new ArrayCollection();
}
/**
{
return $this->rssLimit;
}
+
+ /**
+ * @param TaggingRule $rule
+ *
+ * @return Config
+ */
+ public function addTaggingRule(TaggingRule $rule)
+ {
+ $this->taggingRules[] = $rule;
+
+ return $this;
+ }
+
+ /**
+ * @return ArrayCollection<TaggingRule>
+ */
+ public function getTaggingRules()
+ {
+ return $this->taggingRules;
+ }
}
*/
public function addTag(Tag $tag)
{
+ if ($this->tags->contains($tag)) {
+ return;
+ }
+
$this->tags[] = $tag;
$tag->addEntry($this);
}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+use KPhoen\RulerZBundle\Validator\Constraints as RulerZAssert;
+
+/**
+ * Tagging rule.
+ *
+ * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\TaggingRuleRepository")
+ * @ORM\Table
+ * @ORM\Entity
+ */
+class TaggingRule
+{
+ /**
+ * @var int
+ *
+ * @ORM\Column(name="id", type="integer")
+ * @ORM\Id
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var string
+ *
+ * @Assert\NotBlank()
+ * @RulerZAssert\ValidRule(
+ * allowed_variables={"title", "url", "isArchived", "isStared", "content", "language", "mimetype", "readingTime", "domainName"},
+ * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches"}
+ * )
+ * @ORM\Column(name="rule", type="string", nullable=false)
+ */
+ private $rule;
+
+ /**
+ * @var array
+ *
+ * @Assert\NotBlank()
+ * @ORM\Column(name="tags", type="simple_array", nullable=false)
+ */
+ private $tags = [];
+
+ /**
+ * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Config", inversedBy="taggingRules")
+ */
+ private $config;
+
+ /**
+ * Get id.
+ *
+ * @return int
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set rule.
+ *
+ * @param string $rule
+ *
+ * @return TaggingRule
+ */
+ public function setRule($rule)
+ {
+ $this->rule = $rule;
+
+ return $this;
+ }
+
+ /**
+ * Get rule.
+ *
+ * @return string
+ */
+ public function getRule()
+ {
+ return $this->rule;
+ }
+
+ /**
+ * Set tags.
+ *
+ * @param array<string> $tags
+ *
+ * @return TaggingRule
+ */
+ public function setTags(array $tags)
+ {
+ $this->tags = $tags;
+
+ return $this;
+ }
+
+ /**
+ * Get tags.
+ *
+ * @return array<string>
+ */
+ public function getTags()
+ {
+ return $this->tags;
+ }
+
+ /**
+ * Set config.
+ *
+ * @param Config $config
+ *
+ * @return TaggingRule
+ */
+ public function setConfig(Config $config)
+ {
+ $this->config = $config;
+
+ return $this;
+ }
+
+ /**
+ * Get config.
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Form\DataTransformer;
+
+use Doctrine\Common\Persistence\ObjectManager;
+use Symfony\Component\Form\DataTransformerInterface;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+
+/**
+ * Transforms a comma-separated list to a proper PHP array.
+ * Example: the string "foo, bar" will become the array ["foo", "bar"]
+ */
+class StringToListTransformer implements DataTransformerInterface
+{
+ /**
+ * @var string
+ */
+ private $separator;
+
+ /**
+ * @param string $separator The separator used in the list.
+ */
+ public function __construct($separator = ',')
+ {
+ $this->separator = $separator;
+ }
+
+ /**
+ * Transforms a list to a string.
+ *
+ * @param array|null $list
+ *
+ * @return string
+ */
+ public function transform($list)
+ {
+ if (null === $list) {
+ return '';
+ }
+
+ return implode($this->separator, $list);
+ }
+
+ /**
+ * Transforms a string to a list.
+ *
+ * @param string $string
+ *
+ * @return array|null
+ */
+ public function reverseTransform($string)
+ {
+ if ($string === null) {
+ return null;
+ }
+
+ return array_values(array_filter(array_map('trim', explode($this->separator, $string))));
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+use Wallabag\CoreBundle\Form\DataTransformer\StringToListTransformer;
+
+class TaggingRuleType extends AbstractType
+{
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->add('rule', 'text', array('required' => true))
+ ->add('save', 'submit')
+ ;
+
+ $tagsField = $builder
+ ->create('tags', 'text')
+ ->addModelTransformer(new StringToListTransformer(','));
+
+ $builder->add($tagsField);
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'data_class' => 'Wallabag\CoreBundle\Entity\TaggingRule',
+ ));
+ }
+
+ public function getName()
+ {
+ return 'tagging_rule';
+ }
+}
namespace Wallabag\CoreBundle\Helper;
use Graby\Graby;
+use Psr\Log\LoggerInterface as Logger;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Tools\Utils;
class ContentProxy
{
protected $graby;
+ protected $tagger;
+ protected $logger;
- public function __construct(Graby $graby)
+ public function __construct(Graby $graby, RuleBasedTagger $tagger, Logger $logger)
{
- $this->graby = $graby;
+ $this->graby = $graby;
+ $this->tagger = $tagger;
+ $this->logger = $logger;
}
/**
$entry->setPreviewPicture($content['open_graph']['og_image']);
}
+ try {
+ $this->tagger->tag($entry);
+ } catch (\Exception $e) {
+ $this->logger->error('Error while trying to automatically tag an entry.', array(
+ 'entry_url' => $url,
+ 'error_msg' => $e->getMessage(),
+ ));
+ }
+
return $entry;
}
}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Helper;
+
+use RulerZ\RulerZ;
+
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\CoreBundle\Entity\Tag;
+use Wallabag\CoreBundle\Repository\EntryRepository;
+use Wallabag\CoreBundle\Repository\TagRepository;
+use Wallabag\UserBundle\Entity\User;
+
+class RuleBasedTagger
+{
+ private $rulerz;
+ private $tagRepository;
+ private $entryRepository;
+
+ public function __construct(RulerZ $rulerz, TagRepository $tagRepository, EntryRepository $entryRepository)
+ {
+ $this->rulerz = $rulerz;
+ $this->tagRepository = $tagRepository;
+ $this->entryRepository = $entryRepository;
+ }
+
+ /**
+ * Add tags from rules defined by the user.
+ *
+ * @param Entry $entry Entry to tag.
+ */
+ public function tag(Entry $entry)
+ {
+ $rules = $this->getRulesForUser($entry->getUser());
+
+ foreach ($rules as $rule) {
+ if (!$this->rulerz->satisfies($entry, $rule->getRule())) {
+ continue;
+ }
+
+ foreach ($rule->getTags() as $label) {
+ $tag = $this->getTag($entry->getUser(), $label);
+
+ $entry->addTag($tag);
+ }
+ }
+ }
+
+ /**
+ * Apply all the tagging rules defined by a user on its entries.
+ *
+ * @param User $user
+ *
+ * @return array<Entry> A list of modified entries.
+ */
+ public function tagAllForUser(User $user)
+ {
+ $rules = $this->getRulesForUser($user);
+ $entries = array();
+
+ foreach ($rules as $rule) {
+ $qb = $this->entryRepository->getBuilderForAllByUser($user->getId());
+ $entries = $this->rulerz->filter($qb, $rule->getRule());
+
+ foreach ($entries as $entry) {
+ foreach ($rule->getTags() as $label) {
+ $tag = $this->getTag($user, $label);
+
+ $entry->addTag($tag);
+ $entries[] = $entry;
+ }
+ }
+ }
+
+ return $entries;
+ }
+
+ /**
+ * Fetch a tag for a user.
+ *
+ * @param User $user
+ * @param string $label The tag's label.
+ *
+ * @return Tag
+ */
+ private function getTag(User $user, $label)
+ {
+ $tag = $this->tagRepository->findOneByLabelAndUserId($label, $user->getId());
+
+ if (!$tag) {
+ $tag = new Tag($user);
+ $tag->setLabel($label);
+ }
+
+ return $tag;
+ }
+
+ /**
+ * Retrieves the tagging rules for a given user.
+ *
+ * @param User $user
+ *
+ * @return array<TaggingRule>
+ */
+ private function getRulesForUser(User $user)
+ {
+ return $user->getConfig()->getTaggingRules();
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Operator\Doctrine;
+
+/**
+ * Provides a "matches" operator used for tagging rules.
+ *
+ * It asserts that a given pattern is 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 Matches
+{
+ public function __invoke($subject, $pattern)
+ {
+ if ($pattern[0] === "'") {
+ $pattern = sprintf("'%%%s%%'", substr($pattern, 1, -1));
+ }
+
+ return sprintf('UPPER(%s) LIKE UPPER(%s)', $subject, $pattern);
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Operator\PHP;
+
+/**
+ * Provides a "matches" operator used for tagging rules.
+ *
+ * It asserts that a given pattern is 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 Matches
+{
+ public function __invoke($subject, $pattern)
+ {
+ return stripos($subject, $pattern) !== false;
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Repository;
+
+use Doctrine\ORM\EntityRepository;
+
+class TaggingRuleRepository extends EntityRepository
+{
+}
class: Wallabag\CoreBundle\Helper\ContentProxy
arguments:
- @wallabag_core.graby
+ - @wallabag_core.rule_based_tagger
+ - @logger
+
+ wallabag_core.rule_based_tagger:
+ class: Wallabag\CoreBundle\Helper\RuleBasedTagger
+ arguments:
+ - @rulerz
+ - @wallabag_core.tag_repository
+ - @wallabag_core.entry_repository
+
+ wallabag_core.entry_repository:
+ class: Wallabag\CoreBundle\Repository\EntryRepository
+ factory: [ @doctrine.orm.default_entity_manager, getRepository ]
+ arguments:
+ - WallabagCoreBundle:Entry
+
+ wallabag_core.tag_repository:
+ class: Wallabag\CoreBundle\Repository\TagRepository
+ factory: [ @doctrine.orm.default_entity_manager, getRepository ]
+ arguments:
+ - WallabagCoreBundle:Tag
wallabag_core.registration_confirmed:
class: Wallabag\CoreBundle\EventListener\RegistrationConfirmedListener
arguments:
- %wallabag_url%
- src/Wallabag/CoreBundle/Resources/views/themes/_global/public/img/appicon/apple-touch-icon-152.png
+
+ wallabag.operator.array.matches:
+ class: Wallabag\CoreBundle\Operator\PHP\Matches
+ tags:
+ - { name: rulerz.operator, executor: rulerz.executor.array, operator: matches }
+
+ wallabag.operator.doctrine.matches:
+ class: Wallabag\CoreBundle\Operator\Doctrine\Matches
+ tags:
+ - { name: rulerz.operator, executor: rulerz.executor.doctrine, operator: matches, inline: true }
{{ form_rest(form.pwd) }}
</form>
+ <h2>{% trans %}Tagging rules{% endtrans %}</h2>
+
+ <ul>
+ {% for tagging_rule in app.user.config.taggingRules %}
+ <li>
+ if « {{ tagging_rule.rule }} » then tag as « {{ tagging_rule.tags|join(', ') }} »
+ <a href="{{ path('delete_tagging_rule', {id: tagging_rule.id}) }}" title="{% trans %}Delete{% endtrans %}" class="tool delete icon-trash icon"></a>
+ </li>
+ {% endfor %}
+ </ul>
+
+ <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_tagging_rule) }}>
+ {{ form_errors(form.new_tagging_rule) }}
+
+ <fieldset class="w500p inline">
+ <div class="row">
+ {{ form_label(form.new_tagging_rule.rule) }}
+ {{ form_errors(form.new_tagging_rule.rule) }}
+ {{ form_widget(form.new_tagging_rule.rule) }}
+ </div>
+ </fieldset>
+
+ <fieldset class="w500p inline">
+ <div class="row">
+ {{ form_label(form.new_tagging_rule.tags) }}
+ {{ form_errors(form.new_tagging_rule.tags) }}
+ {{ form_widget(form.new_tagging_rule.tags) }}
+ </div>
+ </fieldset>
+
+ {{ form_rest(form.new_tagging_rule) }}
+ </form>
+
{% if is_granted('ROLE_SUPER_ADMIN') %}
<h2>{% trans %}Add a user{% endtrans %}</h2>
<li class="tab col s3"><a href="#set2">{% trans %}RSS{% endtrans %}</a></li>
<li class="tab col s3"><a href="#set3">{% trans %}User information{% endtrans %}</a></li>
<li class="tab col s3"><a href="#set4">{% trans %}Password{% endtrans %}</a></li>
+ <li class="tab col s3"><a href="#set5">{% trans %}Tagging rules{% endtrans %}</a></li>
{% if is_granted('ROLE_SUPER_ADMIN') %}
- <li class="tab col s3"><a href="#set5">{% trans %}Add a user{% endtrans %}</a></li>
+ <li class="tab col s3"><a href="#set6">{% trans %}Add a user{% endtrans %}</a></li>
{% endif %}
</ul>
</div>
</form>
</div>
- {% if is_granted('ROLE_SUPER_ADMIN') %}
<div id="set5" class="col s12">
+ <div class="row">
+ <div class="input-field col s12">
+ <ul>
+ {% for tagging_rule in app.user.config.taggingRules %}
+ <li>
+ if « {{ tagging_rule.rule }} » then tag as « {{ tagging_rule.tags|join(', ') }} »
+ <a href="{{ path('delete_tagging_rule', {id: tagging_rule.id}) }}" title="{% trans %}Delete{% endtrans %}">
+ <i class="tool grey-text delete mdi-action-delete"></i>
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+
+ {{ form_start(form.new_tagging_rule) }}
+ {{ form_errors(form.new_tagging_rule) }}
+
+ <div class="row">
+ <div class="input-field col s12">
+ {{ form_label(form.new_tagging_rule.rule) }}
+ {{ form_errors(form.new_tagging_rule.rule) }}
+ {{ form_widget(form.new_tagging_rule.rule) }}
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="input-field col s12">
+ {{ form_label(form.new_tagging_rule.tags) }}
+ {{ form_errors(form.new_tagging_rule.tags) }}
+ {{ form_widget(form.new_tagging_rule.tags) }}
+ </div>
+ </div>
+
+ <div class="hidden">{{ form_rest(form.new_tagging_rule) }}</div>
+ <button class="btn waves-effect waves-light" type="submit" name="action">
+ {% trans %}Save{% endtrans %}
+ </button>
+ </form>
+
+ <div class="row">
+ <div class="input-field col s12">
+ <h4>{% trans %}FAQ{% endtrans %}</h4>
+
+ <h5>{% trans %}What does « tagging rules » mean?{% endtrans %}</h5>
+ <p class="help">
+ {% trans %}
+ They are rules used by Wallabag to automatically tag new entries.<br />
+ Each time a new entry is added, all the tagging rules will be used to add
+ the tags you configured, thus saving you the trouble to manually classify
+ your entries.
+ {% endtrans %}
+ </p>
+
+ <h5>{% trans %}How do I use them?{% endtrans %}</h5>
+ <p class="help">
+ {% trans %}
+ Let assume you want to tag new entries as « <i>short reading</i> » when the reading time is inferior to 3 minutes.<br />
+ In that case, you should put « readingTime <= 3 » in the <i>Rule</i> field and « <i>short reading</i> » in the <i>Tags</i>
+ field.<br />
+ Several tags can added simultaneously by separating them by a comma: « <i>short reading, must read</i> »<br />
+ Complex rules can be written by using predefined operators: if « <i>readingTime >= 5 AND domainName = "github.com"</i> » then tag as « <i>long reading, github </i> »
+ {% endtrans %}
+ </p>
+
+ <h5>{% trans %}Which variables and operators can I use to write rules?{% endtrans %}</h5>
+ <p class="help">
+ {% trans %}The following variables and operators can be used to create tagging rules:{% endtrans %}
+
+ <table>
+ <thead>
+ <tr>
+ <th>{% trans %}Variable{% endtrans %}</th>
+ <th>{% trans %}Meaning{% endtrans %}</th>
+ <th>{% trans %}Operator{% endtrans %}</th>
+ <th>{% trans %}Meaning{% endtrans %}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td>title</td>
+ <td>{% trans %}Title of the entry{% endtrans %}</td>
+ <td><=</td>
+ <td>{% trans %}Less than…{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>url</td>
+ <td>{% trans %}URL of the entry{% endtrans %}</td>
+ <td><</td>
+ <td>{% trans %}Strictly less than…{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>isArchived</td>
+ <td>{% trans %}Whether the entry is archived or not{% endtrans %}</td>
+ <td>=></td>
+ <td>{% trans %}Greater than…{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>isStared</td>
+ <td>{% trans %}Whether the entry is starred or not{% endtrans %}</td>
+ <td>></td>
+ <td>{% trans %}Strictly greater than…{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>content</td>
+ <td>{% trans %}The entry's content{% endtrans %}</td>
+ <td>=</td>
+ <td>{% trans %}Equal to…{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>language</td>
+ <td>{% trans %}The entry's language{% endtrans %}</td>
+ <td>!=</td>
+ <td>{% trans %}Not equal to…{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>mimetype</td>
+ <td>{% trans %}The entry's mime-type{% endtrans %}</td>
+ <td>OR</td>
+ <td>{% trans %}One rule or another{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>readingTime</td>
+ <td>{% trans %}The estimated entry's reading time, in minutes{% endtrans %}</td>
+ <td>AND</td>
+ <td>{% trans %}One rule and another{% endtrans %}</td>
+ </tr>
+ <tr>
+ <td>domainName</td>
+ <td>{% trans %}The domain name of the entry{% endtrans %}</td>
+ <td>matches</td>
+ <td>
+ {% trans %}
+ Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />
+ Example: <code>title matches "football"</code>
+ {% endtrans %}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </p>
+ </div>
+ </div>
+ </div>
+
+ {% if is_granted('ROLE_SUPER_ADMIN') %}
+ <div id="set6" class="col s12">
{{ form_start(form.new_user) }}
{{ form_errors(form.new_user) }}
$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.',
+ ),
+ );
+ }
}
$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');
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Tests\Form\DataTransformer;
+
+use Wallabag\CoreBundle\Form\DataTransformer\StringToListTransformer;
+
+class StringToListTransformerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider transformProvider
+ */
+ public function testTransformWithValidData($inputData, $expectedResult)
+ {
+ $transformer = new StringToListTransformer();
+
+ $this->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') ),
+ );
+ }
+}
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;
{
public function testWithEmptyContent()
{
+ $tagger = $this->getTaggerMock();
+ $tagger->expects($this->once())
+ ->method('tag');
+
$graby = $this->getMockBuilder('Graby\Graby')
->setMethods(array('fetchContent'))
->disableOriginalConstructor()
'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());
public function testWithEmptyContentButOG()
{
+ $tagger = $this->getTaggerMock();
+ $tagger->expects($this->once())
+ ->method('tag');
+
$graby = $this->getMockBuilder('Graby\Graby')
->setMethods(array('fetchContent'))
->disableOriginalConstructor()
),
));
- $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());
public function testWithContent()
{
+ $tagger = $this->getTaggerMock();
+ $tagger->expects($this->once())
+ ->method('tag');
+
$graby = $this->getMockBuilder('Graby\Graby')
->setMethods(array('fetchContent'))
->disableOriginalConstructor()
),
));
- $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());
$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();
+ }
}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Tests\Helper;
+
+use Wallabag\CoreBundle\Entity\Config;
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\CoreBundle\Entity\Tag;
+use Wallabag\CoreBundle\Entity\TaggingRule;
+use Wallabag\UserBundle\Entity\User;
+use Wallabag\CoreBundle\Helper\RuleBasedTagger;
+
+class RuleBasedTaggerTest extends \PHPUnit_Framework_TestCase
+{
+ private $rulerz;
+ private $tagRepository;
+ private $entryRepository;
+ private $tagger;
+
+ public function setUp()
+ {
+ $this->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();
+ }
+}
->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();
+ }
}