Integrate bdunogier/guzzle-site-authenticator in core

This commit is contained in:
Yassine Guedidi 2024-02-02 21:56:25 +01:00
parent 8abea942b3
commit 03111e510c
24 changed files with 4555 additions and 65 deletions

View file

@ -30,7 +30,6 @@ class AppKernel extends Kernel
new Craue\ConfigBundle\CraueConfigBundle(),
new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),
new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
new BD\GuzzleSiteAuthenticatorBundle\BDGuzzleSiteAuthenticatorBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
new Http\HttplugBundle\HttplugBundle(),
new Sentry\SentryBundle\SentryBundle(),

View file

@ -34,7 +34,7 @@ services:
Wallabag\CoreBundle\:
resource: '../../src/Wallabag/CoreBundle/*'
exclude: ['../../src/Wallabag/CoreBundle/{Consumer,Controller,Entity,DataFixtures,Redis}', '../../src/Wallabag/CoreBundle/Event/*Event.php']
exclude: ['../../src/Wallabag/CoreBundle/{Consumer,Controller,Entity,ExpressionLanguage,DataFixtures,Redis}', '../../src/Wallabag/CoreBundle/Event/*Event.php']
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
@ -196,7 +196,7 @@ services:
- { name: monolog.logger, channel: graby }
# service alias override
bd_guzzle_site_authenticator.site_config_builder:
Wallabag\CoreBundle\SiteConfig\SiteConfigBuilder:
alias: Wallabag\CoreBundle\SiteConfig\GrabySiteConfigBuilder
GuzzleHttp\Cookie\CookieJar:
@ -204,7 +204,7 @@ services:
Wallabag\CoreBundle\Helper\HttpClientFactory:
calls:
- ["addSubscriber", ["@bd_guzzle_site_authenticator.authenticator_subscriber"]]
- ['addSubscriber', ['@Wallabag\CoreBundle\Guzzle\AuthenticatorSubscriber']]
RulerZ\RulerZ:
alias: rulerz

View file

@ -58,7 +58,6 @@
"ext-tokenizer": "*",
"ext-xml": "*",
"babdev/pagerfanta-bundle": "^3.8",
"bdunogier/guzzle-site-authenticator": "^1.1.0",
"craue/config-bundle": "^2.7.0",
"defuse/php-encryption": "^2.4",
"doctrine/collections": "^1.8",

59
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3676d938a5f11d9556b4c3047f08bf86",
"content-hash": "4ade839f1958082269247bc7ef3ac845",
"packages": [
{
"name": "babdev/pagerfanta-bundle",
@ -143,63 +143,6 @@
},
"time": "2022-12-07T17:46:57+00:00"
},
{
"name": "bdunogier/guzzle-site-authenticator",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/wallabag/guzzle-site-authenticator.git",
"reference": "16a73a973000cde431f45deb6a45b4315fc4d391"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wallabag/guzzle-site-authenticator/zipball/16a73a973000cde431f45deb6a45b4315fc4d391",
"reference": "16a73a973000cde431f45deb6a45b4315fc4d391",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^5.3.1",
"psr/log": "^1.0.0",
"symfony/config": "^4.4|^5.4|^6.0",
"symfony/dependency-injection": "^4.4|^5.4|^6.0",
"symfony/expression-language": "^4.4|^5.4|^6.0",
"symfony/http-kernel": "^4.4|^5.4|^6.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.4.0",
"monolog/monolog": "^2.3",
"nyholm/symfony-bundle-test": "^2.0",
"symfony/phpunit-bridge": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"BD\\GuzzleSiteAuthenticator\\": "lib/",
"BD\\GuzzleSiteAuthenticatorBundle\\": "bundle/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bertrand Dunogier",
"email": "bertrand.dunogier@gmail.com"
}
],
"description": "A guzzle plugin that adds, if necessary, authentication data to requests. Uses credentials and cookies, with login requests to the sites.",
"support": {
"issues": "https://github.com/wallabag/guzzle-site-authenticator/issues",
"source": "https://github.com/wallabag/guzzle-site-authenticator/tree/1.1.0"
},
"time": "2023-08-21T14:33:48+00:00"
},
{
"name": "beberlei/assert",
"version": "v3.3.2",

View file

@ -0,0 +1,24 @@
<?php
namespace Wallabag\CoreBundle\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Wallabag\CoreBundle\Guzzle\FixupMondeDiplomatiqueUriSubscriber;
use Wallabag\CoreBundle\Helper\HttpClientFactory;
class RegisterWallabagGuzzleSubscribersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$definition = $container->getDefinition(HttpClientFactory::class);
// manually add subscribers for some websites
$definition->addMethodCall(
'addSubscriber', [
new Reference(FixupMondeDiplomatiqueUriSubscriber::class),
]
);
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Wallabag\CoreBundle\ExpressionLanguage;
use GuzzleHttp\ClientInterface;
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
class AuthenticatorProvider implements ExpressionFunctionProviderInterface
{
/**
* @var ClientInterface
*/
private $guzzle;
public function __construct(ClientInterface $guzzle)
{
$this->guzzle = $guzzle;
}
public function getFunctions(): array
{
$result = [
$this->getRequestHtmlFunction(),
$this->getXpathFunction(),
$this->getPregMatchFunction(),
];
return $result;
}
private function getRequestHtmlFunction()
{
return new ExpressionFunction(
'request_html',
function () {
throw new \Exception('Not supported');
},
function (array $arguments, $uri, array $options = []) {
return $this->guzzle->get($uri, $options)->getBody();
}
);
}
private function getPregMatchFunction()
{
return new ExpressionFunction(
'preg_match',
function () {
throw new \Exception('Not supported');
},
function (array $arguments, $pattern, $html) {
preg_match($pattern, $html, $matches);
if (2 !== \count($matches)) {
return '';
}
return $matches[1];
}
);
}
private function getXpathFunction()
{
return new ExpressionFunction(
'xpath',
function () {
throw new \Exception('Not supported');
},
function (array $arguments, $xpathQuery, $html) {
$useInternalErrors = libxml_use_internal_errors(true);
$doc = new \DOMDocument();
$doc->loadHTML((string) $html, \LIBXML_NOCDATA | \LIBXML_NOWARNING | \LIBXML_NOERROR);
$xpath = new \DOMXPath($doc);
$domNodeList = $xpath->query($xpathQuery);
if (0 === $domNodeList->length) {
return '';
}
$domNode = $domNodeList->item(0);
libxml_use_internal_errors($useInternalErrors);
if (null === $domNode || null === $domNode->attributes) {
return '';
}
return $domNode->attributes->getNamedItem('value')->nodeValue;
}
);
}
}

View file

@ -0,0 +1,123 @@
<?php
namespace Wallabag\CoreBundle\Guzzle;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Message\RequestInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Wallabag\CoreBundle\SiteConfig\Authenticator\Factory;
use Wallabag\CoreBundle\SiteConfig\SiteConfig;
use Wallabag\CoreBundle\SiteConfig\SiteConfigBuilder;
class AuthenticatorSubscriber implements SubscriberInterface, LoggerAwareInterface
{
// avoid loop when login failed which can just be a bad login/password
// after 2 attempts, we skip the login
public const MAX_RETRIES = 2;
private static $retries = 0;
/** @var SiteConfigBuilder */
private $configBuilder;
/** @var Factory */
private $authenticatorFactory;
/** @var LoggerInterface */
private $logger;
/**
* AuthenticatorSubscriber constructor.
*/
public function __construct(SiteConfigBuilder $configBuilder, Factory $authenticatorFactory)
{
$this->configBuilder = $configBuilder;
$this->authenticatorFactory = $authenticatorFactory;
$this->logger = new NullLogger();
}
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
public function getEvents(): array
{
return [
'before' => ['loginIfRequired'],
'complete' => ['loginIfRequested'],
];
}
public function loginIfRequired(BeforeEvent $event)
{
$config = $this->buildSiteConfig($event->getRequest());
if (false === $config || !$config->requiresLogin()) {
$this->logger->debug('loginIfRequired> will not require login');
return;
}
$client = $event->getClient();
$authenticator = $this->authenticatorFactory->buildFromSiteConfig($config);
if (!$authenticator->isLoggedIn($client)) {
$this->logger->debug('loginIfRequired> user is not logged in, attach authenticator');
$emitter = $client->getEmitter();
$emitter->detach($this);
$authenticator->login($client);
$emitter->attach($this);
}
}
public function loginIfRequested(CompleteEvent $event)
{
$config = $this->buildSiteConfig($event->getRequest());
if (false === $config || !$config->requiresLogin()) {
$this->logger->debug('loginIfRequested> will not require login');
return;
}
$body = $event->getResponse()->getBody();
if (
null === $body
|| '' === $body->getContents()
) {
$this->logger->debug('loginIfRequested> empty body, ignoring');
return;
}
$authenticator = $this->authenticatorFactory->buildFromSiteConfig($config);
$isLoginRequired = $authenticator->isLoginRequired($body);
$this->logger->debug('loginIfRequested> retry #' . self::$retries . ' with login ' . ($isLoginRequired ? '' : 'not ') . 'required');
if ($isLoginRequired && self::$retries < self::MAX_RETRIES) {
$client = $event->getClient();
$emitter = $client->getEmitter();
$emitter->detach($this);
$authenticator->login($client);
$emitter->attach($this);
$event->retry();
++self::$retries;
}
}
/**
* @return SiteConfig|false
*/
private function buildSiteConfig(RequestInterface $request)
{
return $this->configBuilder->buildForHost($request->getHost());
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Wallabag\CoreBundle\Guzzle;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\SubscriberInterface;
/**
* Fixes url encoding of a parameter guzzle fails with.
*/
class FixupMondeDiplomatiqueUriSubscriber implements SubscriberInterface
{
public function getEvents(): array
{
return ['complete' => [['fixUri', 500]]];
}
public function fixUri(CompleteEvent $event)
{
$response = $event->getResponse();
if (!$response->hasHeader('Location')) {
return;
}
$uri = $response->getHeader('Location');
if (false === ($badParameter = strstr($uri, 'retour=http://'))) {
return;
}
$response->setHeader('Location', str_replace($badParameter, urlencode($badParameter), $uri));
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Wallabag\CoreBundle\SiteConfig;
class ArraySiteConfigBuilder implements SiteConfigBuilder
{
/**
* Map of hostname => SiteConfig.
*/
private $configs = [];
public function __construct(array $hostConfigMap = [])
{
foreach ($hostConfigMap as $host => $hostConfig) {
$hostConfig['host'] = $host;
$this->configs[$host] = new SiteConfig($hostConfig);
}
}
public function buildForHost($host)
{
$host = strtolower($host);
if ('www.' === substr($host, 0, 4)) {
$host = substr($host, 4);
}
if (isset($this->configs[$host])) {
return $this->configs[$host];
}
return false;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Wallabag\CoreBundle\SiteConfig\Authenticator;
use GuzzleHttp\ClientInterface;
interface Authenticator
{
/**
* Logs the configured user on the given Guzzle client.
*
* @return self
*/
public function login(ClientInterface $guzzle);
/**
* Checks if we are logged into the site, but without calling the server (e.g. do we have a Cookie).
*
* @return bool
*/
public function isLoggedIn(ClientInterface $guzzle);
/**
* Checks from the HTML of a page if authentication is requested by a grabbed page.
*
* @param string $html
*
* @return bool
*/
public function isLoginRequired($html);
}

View file

@ -0,0 +1,21 @@
<?php
namespace Wallabag\CoreBundle\SiteConfig\Authenticator;
use Wallabag\CoreBundle\SiteConfig\SiteConfig;
/**
* Builds an Authenticator based on a SiteConfig.
*/
class Factory
{
/**
* @return Authenticator
*
* @throw \OutOfRangeException if there are no credentials for this host
*/
public function buildFromSiteConfig(SiteConfig $siteConfig)
{
return new LoginFormAuthenticator($siteConfig);
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace Wallabag\CoreBundle\SiteConfig\Authenticator;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Cookie\CookieJar;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Wallabag\CoreBundle\ExpressionLanguage\AuthenticatorProvider;
use Wallabag\CoreBundle\SiteConfig\SiteConfig;
class LoginFormAuthenticator implements Authenticator
{
/** @var \GuzzleHttp\Client */
protected $guzzle;
/** @var SiteConfig */
private $siteConfig;
public function __construct(SiteConfig $siteConfig)
{
// @todo OptionResolver
$this->siteConfig = $siteConfig;
}
public function login(ClientInterface $guzzle)
{
$postFields = [
$this->siteConfig->getUsernameField() => $this->siteConfig->getUsername(),
$this->siteConfig->getPasswordField() => $this->siteConfig->getPassword(),
] + $this->getExtraFields($guzzle);
$guzzle->post(
$this->siteConfig->getLoginUri(),
['body' => $postFields, 'allow_redirects' => true, 'verify' => false]
);
return $this;
}
public function isLoggedIn(ClientInterface $guzzle)
{
if (($cookieJar = $guzzle->getDefaultOption('cookies')) instanceof CookieJar) {
/** @var \GuzzleHttp\Cookie\SetCookie $cookie */
foreach ($cookieJar as $cookie) {
// check required cookies
if ($cookie->getDomain() === $this->siteConfig->getHost()) {
return true;
}
}
}
return false;
}
public function isLoginRequired($html)
{
$useInternalErrors = libxml_use_internal_errors(true);
// need to check for the login dom element ($options['not_logged_in_xpath']) in the HTML
$doc = new \DOMDocument();
$doc->loadHTML($html);
$xpath = new \DOMXPath($doc);
$loggedIn = $xpath->evaluate((string) $this->siteConfig->getNotLoggedInXpath());
if (false === $loggedIn) {
return false;
}
libxml_use_internal_errors($useInternalErrors);
return $loggedIn->length > 0;
}
/**
* Returns extra fields from the configuration.
* Evaluates any field value that is an expression language string.
*
* @return array
*/
private function getExtraFields(ClientInterface $guzzle)
{
$extraFields = [];
foreach ($this->siteConfig->getExtraFields() as $fieldName => $fieldValue) {
if ('@=' === substr($fieldValue, 0, 2)) {
$expressionLanguage = $this->getExpressionLanguage($guzzle);
$fieldValue = $expressionLanguage->evaluate(
substr($fieldValue, 2),
[
'config' => $this->siteConfig,
]
);
}
$extraFields[$fieldName] = $fieldValue;
}
return $extraFields;
}
/**
* @return ExpressionLanguage
*/
private function getExpressionLanguage(ClientInterface $guzzle)
{
return new ExpressionLanguage(
null,
[new AuthenticatorProvider($guzzle)]
);
}
}

View file

@ -2,8 +2,6 @@
namespace Wallabag\CoreBundle\SiteConfig;
use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfig;
use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfigBuilder;
use Graby\SiteConfig\ConfigBuilder;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

View file

@ -0,0 +1,263 @@
<?php
namespace Wallabag\CoreBundle\SiteConfig;
/**
* Authentication configuration for a site.
*/
class SiteConfig
{
/**
* The site's host name.
*
* @var string
*/
protected $host;
/**
* If the site requires a loogin or not.
*
* @var bool
*/
protected $requiresLogin;
/**
* XPath query used to check if the user was logged in or not.
*
* @var string
*/
protected $notLoggedInXpath;
/**
* URI login data must be sent to.
*
* @var string
*/
protected $loginUri;
/**
* Name of the username field.
*
* @var string
*/
protected $usernameField;
/**
* Name of the password field.
*
* @var string
*/
protected $passwordField;
/**
* Associative array of extra fields to send with the form.
*
* @var array
*/
protected $extraFields = [];
/**
* Username to use for login.
*
* @var string
*/
protected $username;
/**
* Password to use for login.
*
* @var string
*/
protected $password;
/**
* SiteConfig constructor. Sets the properties by name given a hash.
*
* @throws \InvalidArgumentException if a property doesn't exist
*/
public function __construct(array $properties = [])
{
foreach ($properties as $propertyName => $propertyValue) {
if (!property_exists($this, $propertyName)) {
throw new \InvalidArgumentException('Unknown property: "' . $propertyName . '"');
}
$this->$propertyName = $propertyValue;
}
}
/**
* @return bool
*/
public function requiresLogin()
{
return $this->requiresLogin;
}
/**
* @param bool $requiresLogin
*
* @return SiteConfig
*/
public function setRequiresLogin($requiresLogin)
{
$this->requiresLogin = $requiresLogin;
return $this;
}
/**
* @return string
*/
public function getNotLoggedInXpath()
{
return $this->notLoggedInXpath;
}
/**
* @param string $notLoggedInXpath
*
* @return SiteConfig
*/
public function setNotLoggedInXpath($notLoggedInXpath)
{
$this->notLoggedInXpath = $notLoggedInXpath;
return $this;
}
/**
* @return string
*/
public function getLoginUri()
{
return $this->loginUri;
}
/**
* @param string $loginUri
*
* @return SiteConfig
*/
public function setLoginUri($loginUri)
{
$this->loginUri = $loginUri;
return $this;
}
/**
* @return string
*/
public function getUsernameField()
{
return $this->usernameField;
}
/**
* @param string $usernameField
*
* @return SiteConfig
*/
public function setUsernameField($usernameField)
{
$this->usernameField = $usernameField;
return $this;
}
/**
* @return string
*/
public function getPasswordField()
{
return $this->passwordField;
}
/**
* @param string $passwordField
*
* @return SiteConfig
*/
public function setPasswordField($passwordField)
{
$this->passwordField = $passwordField;
return $this;
}
/**
* @return array
*/
public function getExtraFields()
{
return $this->extraFields;
}
/**
* @param array $extraFields
*
* @return SiteConfig
*/
public function setExtraFields($extraFields)
{
$this->extraFields = $extraFields;
return $this;
}
/**
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* @param string $host
*
* @return SiteConfig
*/
public function setHost($host)
{
$this->host = $host;
return $this;
}
public function getUsername()
{
return $this->username;
}
/**
* @return SiteConfig
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @param string $password
*
* @return SiteConfig
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Wallabag\CoreBundle\SiteConfig;
interface SiteConfigBuilder
{
/**
* Builds the SiteConfig for a host.
*
* @param string $host The "www." prefix is ignored.
*
* @throws \OutOfRangeException If there is no config for $host
*
* @return SiteConfig|false
*/
public function buildForHost($host);
}

View file

@ -4,6 +4,7 @@ namespace Wallabag\CoreBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Wallabag\CoreBundle\DependencyInjection\CompilerPass\RegisterWallabagGuzzleSubscribersPass;
use Wallabag\CoreBundle\Import\ImportCompilerPass;
class WallabagCoreBundle extends Bundle
@ -13,5 +14,6 @@ class WallabagCoreBundle extends Bundle
parent::build($container);
$container->addCompilerPass(new ImportCompilerPass());
$container->addCompilerPass(new RegisterWallabagGuzzleSubscribersPass());
}
}

View file

@ -0,0 +1,327 @@
<?php
namespace Tests\Wallabag\CoreBundle\Guzzle;
use GuzzleHttp\Client;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Message\Request;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Subscriber\Mock;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;
use Wallabag\CoreBundle\Guzzle\AuthenticatorSubscriber;
use Wallabag\CoreBundle\SiteConfig\ArraySiteConfigBuilder;
use Wallabag\CoreBundle\SiteConfig\Authenticator\Authenticator;
use Wallabag\CoreBundle\SiteConfig\Authenticator\Factory;
class AuthenticatorSubscriberTest extends TestCase
{
public function testGetEvents()
{
$subscriber = new AuthenticatorSubscriber(
new ArraySiteConfigBuilder(),
new Factory()
);
$events = $subscriber->getEvents();
$this->assertArrayHasKey('before', $events);
$this->assertArrayHasKey('complete', $events);
$this->assertSame('loginIfRequired', $events['before'][0]);
$this->assertSame('loginIfRequested', $events['complete'][0]);
}
public function testLoginIfRequiredNotRequired()
{
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$subscriber = new AuthenticatorSubscriber($builder, new Factory());
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(BeforeEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$subscriber->loginIfRequired($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequired> will not require login', $records[0]['message']);
}
public function testLoginIfRequiredWithNotLoggedInUser()
{
$authenticator = $this->getMockBuilder(Authenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoggedIn')
->willReturn(false);
$authenticator->expects($this->once())
->method('login');
$factory = $this->getMockBuilder(Factory::class)
->disableOriginalConstructor()
->getMock();
$factory->expects($this->once())
->method('buildFromSiteConfig')
->willReturn($authenticator);
$builder = new ArraySiteConfigBuilder(['example.com' => ['requiresLogin' => true]]);
$subscriber = new AuthenticatorSubscriber($builder, $factory);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(BeforeEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$event->expects($this->once())
->method('getClient')
->willReturn($guzzle);
$subscriber->loginIfRequired($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequired> user is not logged in, attach authenticator', $records[0]['message']);
}
public function testLoginIfRequestedNotRequired()
{
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$subscriber = new AuthenticatorSubscriber($builder, new Factory());
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> will not require login', $records[0]['message']);
}
public function testLoginIfRequestedNotRequested()
{
$authenticator = $this->getMockBuilder(Authenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoginRequired')
->willReturn(false);
$factory = $this->getMockBuilder(Factory::class)
->disableOriginalConstructor()
->getMock();
$factory->expects($this->once())
->method('buildFromSiteConfig')
->willReturn($authenticator);
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new AuthenticatorSubscriber($builder, $factory);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('<html><body/></html>')
);
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> retry #0 with login not required', $records[0]['message']);
}
public function testLoginIfRequestedRequested()
{
$authenticator = $this->getMockBuilder(Authenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoginRequired')
->willReturn(true);
$authenticator->expects($this->once())
->method('login');
$factory = $this->getMockBuilder(Factory::class)
->disableOriginalConstructor()
->getMock();
$factory->expects($this->once())
->method('buildFromSiteConfig')
->willReturn($authenticator);
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new AuthenticatorSubscriber($builder, $factory);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('<html><body/></html>')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$event->expects($this->any())
->method('getClient')
->willReturn($guzzle);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> retry #0 with login required', $records[0]['message']);
}
public function testLoginIfRequestedRedirect()
{
$factory = $this->getMockBuilder(Factory::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new AuthenticatorSubscriber($builder, $factory);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
301,
[],
Stream::factory('')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$event->expects($this->any())
->method('getClient')
->willReturn($guzzle);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> empty body, ignoring', $records[0]['message']);
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Tests\Wallabag\CoreBundle\Guzzle;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use PHPUnit\Framework\TestCase;
use Wallabag\CoreBundle\Guzzle\FixupMondeDiplomatiqueUriSubscriber;
class FixupMondeDiplomatiqueUriSubscriberTest extends TestCase
{
public function testGetEvents()
{
$subscriber = new FixupMondeDiplomatiqueUriSubscriber();
$events = $subscriber->getEvents();
$this->assertArrayHasKey('complete', $events);
$this->assertCount(2, $events['complete'][0]);
}
public function testGetEventsWithoutHeaderLocation()
{
$response = new Response(
200,
[
'content-type' => 'text/html',
],
Stream::factory('<html></html>')
);
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$subscriber = new FixupMondeDiplomatiqueUriSubscriber();
$subscriber->fixUri($event);
$this->assertFalse($response->hasHeader('Location'));
}
public function testGetEventsWithNotMachingHeaderLocation()
{
$response = new Response(
200,
[
'content-type' => 'text/html',
'Location' => 'http://example.com',
],
Stream::factory('<html></html>')
);
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$subscriber = new FixupMondeDiplomatiqueUriSubscriber();
$subscriber->fixUri($event);
$this->assertSame('http://example.com', $response->getHeader('Location'));
}
public function testGetEventsWithMachingHeaderLocation()
{
$response = new Response(
200,
[
'content-type' => 'text/html',
'Location' => 'http://example.com/?foo=bar&retour=http://example.com',
],
Stream::factory('<html></html>')
);
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$subscriber = new FixupMondeDiplomatiqueUriSubscriber();
$subscriber->fixUri($event);
$this->assertSame('http://example.com/?foo=bar&retour%3Dhttp%3A%2F%2Fexample.com', $response->getHeader('Location'));
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Tests\Wallabag\CoreBundle\SiteConfig;
use PHPUnit\Framework\TestCase;
use Wallabag\CoreBundle\SiteConfig\ArraySiteConfigBuilder;
use Wallabag\CoreBundle\SiteConfig\SiteConfig;
class ArraySiteConfigBuilderTest extends TestCase
{
public function testItReturnsSiteConfigThatExists()
{
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$res = $builder->buildForHost('www.example.com');
$this->assertInstanceOf(SiteConfig::class, $res);
}
public function testItReturnsFalseOnAHostThatDoesNotExist()
{
$builder = new ArraySiteConfigBuilder(['anotherexample.com' => []]);
$res = $builder->buildForHost('example.com');
$this->assertfalse($res);
}
}

View file

@ -0,0 +1,235 @@
<?php
namespace Tests\Wallabag\CoreBundle\SiteConfig\Authenticator;
use GuzzleHttp\Client;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Subscriber\Mock;
use PHPUnit\Framework\TestCase;
use Wallabag\CoreBundle\SiteConfig\Authenticator\LoginFormAuthenticator;
use Wallabag\CoreBundle\SiteConfig\SiteConfig;
class LoginFormAuthenticatorTest extends TestCase
{
public function testLoginPost()
{
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$siteConfig = new SiteConfig([
'host' => 'example.com',
'loginUri' => 'http://example.com/login',
'usernameField' => 'username',
'passwordField' => 'password',
'extraFields' => [
'action' => 'login',
'foo' => 'bar',
],
'username' => 'johndoe',
'password' => 'unkn0wn',
]);
$auth = new LoginFormAuthenticator($siteConfig);
$res = $auth->login($guzzle);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
public function testLoginPostWithExtraFieldsButEmptyHtml()
{
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('<html></html>')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response, $response]));
$siteConfig = new SiteConfig([
'host' => 'example.com',
'loginUri' => 'http://example.com/login',
'usernameField' => 'username',
'passwordField' => 'password',
'extraFields' => [
'action' => 'login',
'foo' => 'bar',
'security' => '@=xpath(\'substring(//script[contains(text(), "security")]/text(), 112, 10)\', request_html(\'https://aoc.media/\'))',
],
'username' => 'johndoe',
'password' => 'unkn0wn',
]);
$auth = new LoginFormAuthenticator($siteConfig);
$res = $auth->login($guzzle);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
// testing preg_match
public function testLoginPostWithExtraFieldsWithRegex()
{
$response = $this->getMockBuilder(Response::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->any())
->method('getBody')
->willReturn(file_get_contents(__DIR__ . '/../../fixtures/aoc.media.html'));
$response->expects($this->any())
->method('getStatusCode')
->willReturn(200);
$client = $this->getMockBuilder(Client::class)
->disableOriginalConstructor()
->getMock();
$client->expects($this->any())
->method('post')
->with(
$this->equalTo('https://aoc.media/wp-admin/admin-ajax.php'),
$this->equalTo([
'body' => [
'nom' => 'johndoe',
'password' => 'unkn0wn',
'security' => 'c506c1b8bc',
'action' => 'login_user',
],
'allow_redirects' => true,
'verify' => false,
])
)
->willReturn($response);
$client->expects($this->any())
->method('get')
->with(
$this->equalTo('https://aoc.media/'),
$this->equalTo([])
)
->willReturn($response);
$siteConfig = new SiteConfig([
'host' => 'aoc.media',
'loginUri' => 'https://aoc.media/wp-admin/admin-ajax.php',
'usernameField' => 'nom',
'passwordField' => 'password',
'extraFields' => [
'action' => 'login_user',
'security' => '@=preg_match(\'/security\\\":\\\"([a-z0-9]+)\\\"/si\', request_html(\'https://aoc.media/\'))',
],
'username' => 'johndoe',
'password' => 'unkn0wn',
]);
$auth = new LoginFormAuthenticator($siteConfig);
$res = $auth->login($client);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
public function testLoginPostWithExtraFieldsWithData()
{
$response = $this->getMockBuilder(Response::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->any())
->method('getBody')
->willReturn(file_get_contents(__DIR__ . '/../../fixtures/nextinpact-login.html'));
$response->expects($this->any())
->method('getStatusCode')
->willReturn(200);
$client = $this->getMockBuilder(Client::class)
->disableOriginalConstructor()
->getMock();
$client->expects($this->any())
->method('post')
->with(
$this->equalTo('https://compte.nextinpact.com/Account/Login'),
$this->equalTo([
'body' => [
'UserName' => 'johndoe',
'Password' => 'unkn0wn',
'__RequestVerificationToken' => 's6x2QcnQDUL92mkKSi_JuUBXcgUYx_Plf-KyQ2eJypKAjQZIeTvaFHOsfEdTrcSXt3dt2CW39V7r9V16LUtvjszodAU1',
'returnUrl' => 'https://www.nextinpact.com/news/102835-pour-cour-comptes-fonctionnement-actuel-vote-par-internet-nest-pas-satisfaisant.htm',
],
'allow_redirects' => true,
'verify' => false,
])
)
->willReturn($response);
$client->expects($this->any())
->method('get')
->with(
$this->equalTo('https://compte.nextinpact.com/Account/Login?http://www.nextinpact.com/'),
$this->equalTo([
'headers' => [
'X-Requested-With' => 'XMLHttpRequest',
],
])
)
->willReturn($response);
$siteConfig = new SiteConfig([
'host' => 'nextinpact.com',
'loginUri' => 'https://compte.nextinpact.com/Account/Login',
'usernameField' => 'UserName',
'passwordField' => 'Password',
'extraFields' => [
'__RequestVerificationToken' => '@=xpath(\'//form[@action="/Account/Login"]/input[@name="__RequestVerificationToken"]\', request_html(\'https://compte.nextinpact.com/Account/Login?http://www.nextinpact.com/\', {\'headers\': {\'X-Requested-With\':\'XMLHttpRequest\'}}))',
'returnUrl' => 'https://www.nextinpact.com/news/102835-pour-cour-comptes-fonctionnement-actuel-vote-par-internet-nest-pas-satisfaisant.htm',
],
'username' => 'johndoe',
'password' => 'unkn0wn',
]);
$auth = new LoginFormAuthenticator($siteConfig);
$res = $auth->login($client);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
public function testLoginWithBadSiteConfigNotLoggedInData()
{
$siteConfig = new SiteConfig([
'host' => 'nextinpact.com',
'loginUri' => 'https://compte.nextinpact.com/Account/Login',
'usernameField' => 'UserName',
'username' => 'johndoe',
'password' => 'unkn0wn',
]);
$auth = new LoginFormAuthenticator($siteConfig);
$loginRequired = $auth->isLoginRequired(file_get_contents(__DIR__ . '/../../fixtures/nextinpact-login.html'));
$this->assertFalse($loginRequired);
}
public function testLoginWithGoodSiteConfigNotLoggedInData()
{
$siteConfig = new SiteConfig([
'host' => 'nextinpact.com',
'loginUri' => 'https://compte.nextinpact.com/Account/Login',
'usernameField' => 'UserName',
'username' => 'johndoe',
'password' => 'unkn0wn',
'notLoggedInXpath' => '//h2[@class="title_reserve_article"]',
]);
$auth = new LoginFormAuthenticator($siteConfig);
$loginRequired = $auth->isLoginRequired(file_get_contents(__DIR__ . '/../../fixtures/nextinpact-article.html'));
$this->assertTrue($loginRequired);
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Tests\Wallabag\CoreBundle\SiteConfig;
use PHPUnit\Framework\TestCase;
use Wallabag\CoreBundle\SiteConfig\SiteConfig;
class SiteConfigTest extends TestCase
{
public function testInitSiteConfig()
{
$config = new SiteConfig([]);
$this->assertInstanceOf(SiteConfig::class, $config);
}
public function testUnknownProperty()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown property: "bad"');
new SiteConfig(['bad' => true]);
}
public function testInitSiteConfigWillFullOptions()
{
$config = new SiteConfig([
'host' => 'example.com',
'requiresLogin' => true,
'notLoggedInXpath' => '//all',
'loginUri' => 'https://example.com/login',
'usernameField' => 'username',
'passwordField' => 'password',
'extraFields' => [
'action' => 'login',
'foo' => 'bar',
],
'username' => 'johndoe',
'password' => 'unkn0wn',
]);
$this->assertInstanceOf(SiteConfig::class, $config);
}
}

View file

@ -0,0 +1,860 @@
<!doctype html>
<html lang="fr-FR" prefix="og: http://ogp.me/ns#">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Poppins:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Playfair+Display:400,400i,700,700i,900,900i" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="shortcut icon" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/favicon.ico" type="image/x-icon">
<link rel="icon" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/favicon.png" type="image/png">
<link rel="icon" sizes="16x16" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/favicon-16x16.png"
type="image/png">
<link rel="icon" sizes="64x64" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/favicon-32x32.png"
type="image/png">
<link rel="icon" sizes="96x96" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/favicon-96x96.png"
type="image/png">
<link rel="apple-touch-icon" sizes="152x152"
href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="60x60" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="76x76" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114"
href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120"
href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144"
href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-144x144.png">
<link rel="icon" sizes="192x192" href="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/apple-icon-192x192.png"
type="image/png">
<meta name="msapplication-TileImage" content="https://aoc.media/wp-content/themes/AOC/dist/images/favicons/ms-icon-144x144.png">
<meta name="msapplication-TileColor" content="#FFFFFF">
<title>AOC media - Analyse Opinion Critique | AOC est un quotidien d&#039;auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes</title>
<link rel="dns-prefetch" href="//tracker.wpserveur.net">
<script type="text/javascript">
document.cookie = "WPServeur-js=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"
</script>
<!-- This site is optimized with the Yoast SEO plugin v6.2 - https://yoa.st/1yg?utm_content=6.2 -->
<meta name="description" content="AOC est un quotidien d&#039;auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes"/>
<link rel="canonical" href="https://aoc.media/" />
<meta property="og:locale" content="fr_FR" />
<meta property="og:type" content="website" />
<meta property="og:title" content="AOC media - Analyse Opinion Critique | AOC est un quotidien d&#039;auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes" />
<meta property="og:description" content="AOC est un quotidien d&#039;auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes" />
<meta property="og:url" content="https://aoc.media/" />
<meta property="og:site_name" content="AOC media - Analyse Opinion Critique" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content="AOC est un quotidien d&#039;auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes" />
<meta name="twitter:title" content="AOC media - Analyse Opinion Critique | AOC est un quotidien d&#039;auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes" />
<meta name="twitter:site" content="@AOC_media" />
<script type='application/ld+json'>{"@context":"http:\/\/schema.org","@type":"WebSite","@id":"#website","url":"https:\/\/aoc.media\/","name":"AOC media - Analyse Opinion Critique","potentialAction":{"@type":"SearchAction","target":"https:\/\/aoc.media\/?s={search_term_string}","query-input":"required name=search_term_string"}}</script>
<script type='application/ld+json'>{"@context":"http:\/\/schema.org","@type":"Organization","url":"https:\/\/aoc.media\/","sameAs":["https:\/\/www.facebook.com\/analyse.opinion.critique\/","https:\/\/www.instagram.com\/aoc.media\/","https:\/\/www.youtube.com\/channel\/UCRX8xRrt4Wd-4wA9EIOKjjA","https:\/\/twitter.com\/AOC_media"],"@id":"#organization","name":"AOC media","logo":"https:\/\/aoc.media\/wp-content\/uploads\/2018\/08\/logo-aoc-grand.png"}</script>
<!-- / Yoast SEO plugin. -->
<meta name="description" content="AOC est un quotidien d&#039;auteurs en ligne. Conçu par des journalistes, AOC est un journal écrit par des chercheurs, des écrivains, des intellectuels, des artistes et... des journalistes. AOC est l&#039;acronyme d&#039;Analyse Opinion Critique." />
<link rel="canonical" href="https://aoc.media/" />
<script type="application/ld+json">{"@context" : "https://schema.org","@type" : "Organization","logo": "https:\/\/aoc.media\/wp-content\/uploads\/2018\/01\/22499797-1480914805322502-3874064718649884672-n.jpg","name" : "AOC","url" : "https:\/\/aoc.media","sameAs" : ["https:\/\/www.facebook.com\/analyse.opinion.critique\/", "https:\/\/twitter.com\/@AOC_media", "https:\/\/www.instagram.com\/aoc.media\/", "https:\/\/www.youtube.com\/channel\/UCRX8xRrt4Wd-4wA9EIOKjjA"]}</script>
<script type="application/ld+json">{
"@context": "https://schema.org",
"@type": "WebSite",
"url" : "https:\/\/aoc.media",
"potentialAction": {
"@type": "SearchAction",
"target": "https:\/\/aoc.media\/?s={search_term_string}",
"query-input": "required name=search_term_string"
}
}</script>
<meta property="og:url" content="https://aoc.media/" />
<meta property="og:site_name" content="AOC media - Analyse Opinion Critique" />
<meta property="og:locale" content="fr_FR" />
<meta property="og:type" content="website" />
<meta property="og:title" content="AOC media - Analyse Opinion Critique" />
<meta property="og:description" content="AOC est un quotidien d&#039;auteurs en ligne. Conçu par des journalistes, AOC est un journal écrit par des chercheurs, des écrivains, des intellectuels, des artistes et... des journalistes. AOC est l&#039;acronyme d&#039;Analyse Opinion Critique." />
<meta property="fb:pages" content="138301850146155" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@AOC_media" />
<meta name="twitter:creator" content="@AOC_media" />
<meta name="twitter:title" content="AOC media - Analyse Opinion Critique" />
<meta name="twitter:description" content="AOC est un quotidien d&#039;auteurs en ligne. Conçu par des journalistes, AOC est un journal écrit par des chercheurs, des écrivains, des intellectuels, des artistes et... des journalistes. AOC est l&#039;acronyme d&#039;Analyse Opinion Critique." />
<link rel='dns-prefetch' href='//s.w.org' />
<link rel='stylesheet' id='pmpro_frontend-css' href='https://aoc.media/wp-content/plugins/paid-memberships-pro/css/frontend.css' type='text/css' media='screen' />
<link rel='stylesheet' id='pmpro_print-css' href='https://aoc.media/wp-content/plugins/paid-memberships-pro/css/print.css' type='text/css' media='print' />
<link rel='stylesheet' id='printomatic-css-css' href='https://aoc.media/wp-content/plugins/print-o-matic/css/style.css' type='text/css' media='all' />
<link rel='stylesheet' id='cookie-consent-style-css' href='https://aoc.media/wp-content/plugins/uk-cookie-consent/assets/css/style.css' type='text/css' media='all' />
<link rel='stylesheet' id='sage/css-css' href='https://aoc.media/wp-content/themes/AOC/dist/styles/main-42f1097a32.css' type='text/css' media='all' />
<script type='text/javascript' src='https://aoc.media/wp-includes/js/jquery/jquery.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-includes/js/jquery/jquery-migrate.min.js'></script>
<link rel="next" href="https://aoc.media/page/2/" />
<link rel='https://api.w.org/' href='https://aoc.media/wp-json/' />
<style id="ctcc-css" type="text/css" media="screen">
#catapult-cookie-bar {
box-sizing: border-box;
max-height: 0;
opacity: 0;
z-index: 99999;
overflow: hidden;
color: #8a929b;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background-color: #ffffff;
}
#catapult-cookie-bar a {
color: #8a929b;
}
#catapult-cookie-bar .x_close span {
background-color: #ffffff;
}
button#catapultCookie {
background:#151515;
color: #ffffff;
border: 0; padding: 6px 9px; border-radius: 3px;
}
#catapult-cookie-bar h3 {
color: #8a929b;
}
.has-cookie-bar #catapult-cookie-bar {
opacity: 1;
max-height: 999px;
min-height: 30px;
}</style> <style type="text/css">.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-108002367-1', 'auto');
ga('send', 'pageview');
</script>
</head>
<body class="home blog">
<!--[if IE]>
<div class="alert alert-warning">
You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience. </div>
<![endif]-->
<header class="banner ">
<div id="banner-account">
<a href="https://aoc.media/offers/">S'abonner</a>
</div>
<!--div class="container"-->
<nav class="nav-primary navbar navbar-toggleable-md navbar-light bg-faded" role="navigation">
<div class="navbar-header col-md-2 col-sm-2 col-2">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false"
aria-label="Toggle navigation">
Menu
</button>
</div>
<div class="navbar-header-right col-md-6 col-sm-6 col-6">
<ul id="menu-mobile">
<li><a href="#" class="connexion" data-toggle="modal" data-target="#popup-connexion">Connexion</a></li>
<!-- Button trigger modal -->
</ul>
<ul>
<li><a href="#" data-toggle="modal" data-target="#popup-newsletter">Newsletter</a></li>
<li><a href="https://aoc.media/offers/">Abonnement</a></li>
<li><a href="#" class="connexion" data-toggle="modal" data-target="#popup-connexion">Connexion</a></li>
<!-- Button trigger modal -->
</ul>
</div>
<div class="collapse navbar-collapse" id="navbarNav">
<div class="modal-dialog-wrapper">
<div class="container">
<div class="row">
<a href="javascript:void(0)" class="closebtn">Fermer</a>
<div class="col-md-6 col-xs-12">
<div class="menu-navigation-container"><ul id="menu-navigation" class="nav"><li id="menu-item-33" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-33 analyse"><a href="https://aoc.media/./analyse/">Analyse</a></li>
<li id="menu-item-35" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-35 opinion"><a href="https://aoc.media/./opinion/">Opinion</a></li>
<li id="menu-item-34" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-34 critique"><a href="https://aoc.media/./critique/">Critique</a></li>
<li id="menu-item-519" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-519 fiction"><a href="https://aoc.media/./fiction/">Fiction</a></li>
<li id="menu-item-518" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-518 entretien"><a href="https://aoc.media/./entretien/">Entretien</a></li>
<li id="menu-item-45" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-45 leblog"><a href="https://aoc.media/leblog/">Le blog</a></li>
</ul></div> </div>
<div class="col-md-offset-2 col-md-4 col-xs-12">
<div class="menu-authors-container">
<h3 class="title-authors auteurs">
<a href="https://aoc.media/auteurs/">Auteurs</a>
</h3>
<ul id="menu-authors" class="nav">
<li>
<a href="https://aoc.media/auteur/gisela-bergonzoniaoc-media/">
Gisela Bergonzoni </a>
</li>
<li>
<a href="https://aoc.media/auteur/philippe-marliere/">
Philippe Marlière </a>
</li>
<li>
<a href="https://aoc.media/auteur/francois-dubet/">
François Dubet </a>
</li>
<li>
<a href="https://aoc.media/auteur/robert-menasseaoc-media/">
Robert Menasse </a>
</li>
<li>
<a href="https://aoc.media/auteur/bourgois/">
Raphaël Bourgois </a>
</li>
<li>
<a href="https://aoc.media/auteur/fabrice-thumerelaoc-media/">
Fabrice Thumerel </a>
</li>
<li>
<a href="https://aoc.media/auteur/bouchet-saulnieraoc-media/">
Francoise Bouchet-Saulnier </a>
</li>
<li>
<a href="https://aoc.media/auteur/serge-paugamaoc-media/">
Serge Paugam </a>
</li>
<li>
<a href="https://aoc.media/auteur/kechichian-patrick/">
Patrick Kéchichian </a>
</li>
<li>
<a href="https://aoc.media/auteur/ziad-majedaoc-media/">
Ziad Majed </a>
</li>
</ul>
</div>
</div>
</div>
<div class="row footer-open">
<div class="col-md-6 col-xs-12">
<p>Suivez-nous sur les réseaux sociaux</p>
<ul class="social-open">
<li><a href="https://www.facebook.com/analyse.opinion.critique/" target="_blank"><i class="fa fa-facebook"></i></a></li>
<li><a href="https://www.instagram.com/aoc.media" target="_blank"><i class="fa fa-instagram"></i></a></li>
<li><a href="https://twitter.com/AOC_media" target="_blank"><i class="fa fa-twitter"></i></a></li>
</ul>
</div>
<div class="col-md-6 col-xs-12">
<ul class="static-pages">
<li><a href="https://aoc.media/a-propos-de-aoc/">Qui sommes-nous</a></li>
<li><a href="https://aoc.media/offers/">Abonnez-vous</a></li>
<li><a id="link-news-md" href="#" data-toggle="modal" data-target="#popup-newsletter">Newsletter</a></li>
<li><a href="https://aoc.media/mention-legales/">Mentions légales</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</nav>
<!--/div-->
</header>
<div class="wrap" role="document"><!-- container -->
<!--div class="content row"-->
<main class="main ">
<div class="page-header" style="display: none;">
<h1>Home</h1>
</div>
<section style="display: none;">
<div class="container">
<h1 class="logo">
<a href="https://aoc.media/" title="AOC media &#8211; Analyse Opinion Critique" rel="home">
<img src="https://aoc.media/wp-content/themes/AOC/dist/images/logo.svg" alt="AOC" />
</a>
</h1>
</div>
</section>
<section class="header">
<h1 class="logo">
<a href="https://aoc.media/./analyse/" class="log analyse">
<span class="let"><img src="https://aoc.media/wp-content/themes/AOC/dist/images/a.svg" alt="A" /></span>
<span class="cat">Analyse</span>
</a>
<a href="https://aoc.media/./opinion/" class="log opinion">
<span class="let"><img src="https://aoc.media/wp-content/themes/AOC/dist/images/o.svg" alt="O" /></span>
<span class="cat">Opinion</span>
</a>
<a href="https://aoc.media/./critique/" class="log critique">
<span class="let"><img src="https://aoc.media/wp-content/themes/AOC/dist/images/c.svg" alt="C" /></span>
<span class="cat">Critique</span>
</a>
</h1>
</section>
<div class="wrapper_posts">
<section class="section today">
<div class="container">
<div class="row ">
<div class="col-lg-2">
<div class="date-article">
<!-- date -->
<span>
Aujourdhui
</span>
10.12 </div>
</div>
<div class="col-lg-9">
<div class="row">
<!-- loop post -->
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./analyse/">Analyse</a></h3>
<h2 class="title"><a href="https://aoc.media/analyse/2018/12/10/transformation-coleres-politiques-possible/">La transformation des colères en politiques est-elle possible ?</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/francois-dubet/"><strong>François Dubet</strong></a></h4>
<a href="https://aoc.media/analyse/2018/12/10/transformation-coleres-politiques-possible/" class="desc-4">Plutôt que de se demander si le mouvement des Gilets Jaunes est justifié ou non, à gauche ou à droite, autoritaire ou démocratique… il faut dabord observer que cette mobilisation est lexpression des sentiments dinjustice engendrés par le régime des inégalités multiples, mais quil ne dispose ni des cadres, ni des relais organisationnels et politiques, ni même de lidéologie qui structurent les mouvements sociaux.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./opinion/">Opinion</a></h3>
<h2 class="title"><a href="https://aoc.media/opinion/2018/12/10/gilets-jaunes-discredit-de-democratie-representative/">Les Gilets Jaunes ou le discrédit de la démocratie représentative</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/philippe-marliere/"><strong>Philippe Marlière</strong></a></h4>
<a href="https://aoc.media/opinion/2018/12/10/gilets-jaunes-discredit-de-democratie-representative/" class="desc-4">Au-delà de multiples revendications politiques plus ou moins claires et cohérentes, les Gilets Jaunes expriment surtout une critique radicale du régime de représentation politique. Dans leur modus operandi, ils chamboulent deux siècles daction politique ; ils en piétinent les règles et la bienséance.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./critique/">Critique</a></h3>
<h2 class="title"><a href="https://aoc.media/critique/2018/12/10/goncalo-m-tavares-recherche-de-linoui/">Gonçalo M. Tavares ou la recherche de l&rsquo;inouï</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/gisela-bergonzoniaoc-media/"><strong>Gisela Bergonzoni</strong></a></h4>
<a href="https://aoc.media/critique/2018/12/10/goncalo-m-tavares-recherche-de-linoui/" class="desc-4">Avec <i>Une jeune fille perdue dans le siècle à la recherche de son père</i>, Gonçalo M. Tavares s'avère fidèle à lui-même. On retrouve dans le récit à la fois la tentation picaresque d'un des plus grands auteurs portugais actuel, en même temps qu'un goût prononcé pour l'absurde et ces personnages aux obstinations beckettiennes. Livre choral, <i>Une jeune fille perdue...</i> amuse d'abord, interroge ensuite, à la manière des ces événements comiques-tragiques dont le XXe siècle fut le théâtre, des événements auxquels il est constamment fait référence dans l'ouvrage.</a>
</article>
</div>
</div>
</div>
</div>
<div class="row justify-content-lg-end">
<div class="col-lg-10">
<!-- Image pub -->
<a href="https://www.editionsladecouverte.fr/catalogue/index-R__sonance-9782707193162.html" target="_blank">
<img src="https://aoc.media/wp-content/uploads/2018/12/rosa-aoc.jpg" alt="">
</a>
<span class="pub">publicité</span>
</div>
</div>
</div>
</section>
<section class="section wide">
<div class="container">
<div class="row row-category">
<div class="col-lg-2">
<div class="date-article">
<span>
dimanche </span>
09.12 </div>
</div>
<div class="col-lg-9">
<article class="article">
<h3 class="category"><a href="https://aoc.media/./fiction/">Fiction</a></h3>
<h2 class="title"><a href="https://aoc.media/fiction/2018/12/09/la-capitale/">La capitale</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/robert-menasseaoc-media/"><strong>Robert Menasse</strong></a></h4>
<a href="https://aoc.media/fiction/2018/12/09/la-capitale/"><div class="desc-9">Tout commence, dans le prologue de <i>La Capitale</i>, par un cochon semant la panique dans le centre de Bruxelles. Panique qui relie les personnages par un fil. Fil que lécrivain autrichien Robert Menasse samuse à emmêler, dérouler, amplifier, dès le premier chapitre. Premier chapitre dans lequel retentit un coup de feu.
AOC continue sa série de Noël avec ces premières pages traduites par Olivier Mannoni, à paraître en janvier chez Verdier, dun roman burlesque mais noir, exubérant mais savamment construit, incroyablement divers mais tenu par ce fil. Une satire des institutions européennes, mais aussi un hommage à lEurope et à sa capitale.
</div></a>
</article>
</div>
</div>
</div>
</section>
<section class="section wide">
<div class="container">
<div class="row row-category">
<div class="col-lg-2">
<div class="date-article">
<span>
samedi </span>
08.12 </div>
</div>
<div class="col-lg-9">
<article class="article">
<h3 class="category"><a href="https://aoc.media/./entretien/">Entretien</a></h3>
<h2 class="title"><a href="https://aoc.media/entretien/2018/12/08/mireille-delmas-marty-70-ans-apres-declaration-universelle-droits-de-lhomme-manque-cest-mode-demploi/">Mireille Delmas-Marty : « 70 ans après la Déclaration universelle des droits de l&rsquo;homme, ce qui manque cest le mode demploi »</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/bourgois/"><strong>Raphaël Bourgois</strong></a></h4>
<a href="https://aoc.media/entretien/2018/12/08/mireille-delmas-marty-70-ans-apres-declaration-universelle-droits-de-lhomme-manque-cest-mode-demploi/"><div class="desc-9">Adoptée le 10 décembre 1948, la Déclaration universelle des droits de lhomme offrait la promesse d'un nouvel ordre international fondé sur les droits humains. À l'heure de célébrer son 70e anniversaire, le sentiment est plutôt à la déception. Face aux défis actuels du réchauffement climatique ou des migrations, la défense des droits humains ne semble plus au centre des préoccupations. Ce n'est pourtant pas une raison pour parler d'échec selon la grande juriste Mireille Delmas-Marty qui nous invite à regarder le chemin parcouru pour mieux relever les défis de demain.</div></a>
</article>
</div>
</div>
</div>
</section>
<section class="section list">
<div class="container">
<div class="row row-category">
<div class="col-lg-2">
<div class="date-article">
<!-- date -->
<span>
vendredi </span>
07.12 </div>
</div>
<div class="col-lg-9">
<div class="row">
<!-- loop post -->
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./analyse/">Analyse</a></h3>
<h2 class="title"><a href="https://aoc.media/analyse/2018/12/07/face-mepris-social-revanche-invisibles/">Face au mépris social, la revanche des invisibles</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/serge-paugamaoc-media/"><strong>Serge Paugam</strong></a></h4>
<a href="https://aoc.media/analyse/2018/12/07/face-mepris-social-revanche-invisibles/" class="desc-4">Publié il y a 25 ans, un ouvrage semble avoir été tout particulièrement annonciateur du malaise social qui s'exprime à la faveur du mouvement des gilets jaunes : <i>La Misère du monde</i>. Et, au-delà de ce livre collectif dirigé par Pierre Bourdieu, les travaux ultérieurs ne manquent pas, ni les concepts « misère de position », «exclusion de l'intérieur », «intégration fragilisée »... qui permettent de comprendre l'invisibilisation des personnes qui manifestent aujourd'hui.
</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./opinion/">Opinion</a></h3>
<h2 class="title"><a href="https://aoc.media/opinion/2018/12/07/guerre-contre-terrorisme-nouvel-ordre-in-humanitaire/">La guerre contre le terrorisme ou le nouvel ordre in-humanitaire</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/bouchet-saulnieraoc-media/"><strong>Francoise Bouchet-Saulnier</strong></a></h4>
<a href="https://aoc.media/opinion/2018/12/07/guerre-contre-terrorisme-nouvel-ordre-in-humanitaire/" class="desc-4">On célèbrera le 10 décembre le 70<sup>e</sup> anniversaire de la Déclaration universelle des droits de l'homme. Mais à l'heure de la guerre contre le terrorisme, le droit international semble peu à peu changer de logique pour criminaliser l'espace civil, au détriment des victimes de ces conflits et des humanitaires leur venant en aide. Au risque de revenir sur la règle la plus ancienne du droit humanitaire : lobligation de soigner lennemi blessé et hors de combat.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./critique/">Critique</a></h3>
<h2 class="title"><a href="https://aoc.media/critique/2018/12/07/poesie-mur-a-propos-demmanuele-jawad/">La poésie fait le mur à propos dEmmanuèle Jawad</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/fabrice-thumerelaoc-media/"><strong>Fabrice Thumerel</strong></a></h4>
<a href="https://aoc.media/critique/2018/12/07/poesie-mur-a-propos-demmanuele-jawad/" class="desc-4">Le courant textualiste en poésie serait désormais derrière nous, en témoigne l'œuvre dEmmanuèle Jawad, aujourd'hui complétée par <i>Carnets de murs</i>. Renouant avec le message militant et engagé de la poésie moderne, le texte devient à « une écriture plastique de lexil » pour sublimer le grand drame de notre siècle : les espoirs des populations déplacées douchés par des kilomètres de murailles.</a>
</article>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="section list">
<div class="container">
<div class="row row-category">
<div class="col-lg-2">
<div class="date-article">
<!-- date -->
<span>
jeudi </span>
06.12 </div>
</div>
<div class="col-lg-9">
<div class="row">
<!-- loop post -->
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./analyse/">Analyse</a></h3>
<h2 class="title"><a href="https://aoc.media/analyse/2018/12/06/syrie-regime-de-violence-fin/">En Syrie, le régime de la violence sans fin</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/ziad-majedaoc-media/"><strong>Ziad Majed</strong></a></h4>
<a href="https://aoc.media/analyse/2018/12/06/syrie-regime-de-violence-fin/" class="desc-4">Pourquoi Bachar Al-Assad mène-t-il, depuis 2011, une guerre contre son propre peuple ? Par ce déchainement de violence, le dictateur sérige en « maître » éternel de la Syrie et du Moyen-Orient. Il ne pille, ne déporte, nemprisonne, ne torture, naffame et ne viole pas tout le monde mais suffisamment pour que les rescapés portent l'empreinte de son pouvoir absolu.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./opinion/">Opinion</a></h3>
<h2 class="title"><a href="https://aoc.media/opinion/2018/12/06/lordre-le-retour/">Lordre, le retour</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/bertrand-leclairaoc-media/"><strong>Bertrand Leclair</strong></a></h4>
<a href="https://aoc.media/opinion/2018/12/06/lordre-le-retour/" class="desc-4">On aurait tort de ne pas analyser dans le mouvement des « gilets jaunes » ce qui ressemble à une fête : une dynamique d'euphorie collective dans laquelle les interdits sont bravés et la communauté nationale ressoudée. Si l'aspect carnavalesque du mouvement rappelle les festivités de mai 68, ces deux périodes divergent néanmoins par l'« air du temps » qu'elles épousent : le fond de cet air, rouge en 1968, s'est depuis considérablement bruni.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./critique/">Critique</a></h3>
<h2 class="title"><a href="https://aoc.media/critique/2018/12/06/gerard-mace-colporteur-de-litterature/">Gérard Macé, colporteur de littérature</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/kechichian-patrick/"><strong>Patrick Kéchichian</strong></a></h4>
<a href="https://aoc.media/critique/2018/12/06/gerard-mace-colporteur-de-litterature/" class="desc-4">Par son <i>Colportage</i>, Gérard Macé construit une poétique des mirages, dessinant des contours incertains et revendiqués comme tels. Poète implicite et dispersé, sans manifeste ni théorie préalable, il pratique un art de la divagation.</a>
</article>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="section list">
<div class="container">
<div class="row row-category">
<div class="col-lg-2">
<div class="date-article">
<!-- date -->
<span>
mercredi </span>
05.12 </div>
</div>
<div class="col-lg-9">
<div class="row">
<!-- loop post -->
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./analyse/">Analyse</a></h3>
<h2 class="title"><a href="https://aoc.media/analyse/2018/12/05/de-lillisibilite-champ-politique/">De l&rsquo;illisibilité du champ politique</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/gerard-maugeraoc-media/"><strong>Gérard Mauger</strong></a></h4>
<a href="https://aoc.media/analyse/2018/12/05/de-lillisibilite-champ-politique/" class="desc-4">S'il est une chose que vient, entre autres, souligner le mouvement des Gilets Jaunes, c'est que l'élection d'Emmanuel Macron n'a pas été perçue par tous comme le « bouleversement politique » qu'y ont vu les éditorialistes. La déconfiture essuyée par les Républicains et le Parti Socialiste aux élections présidentielles témoigne toutefois d'un moment de redéfinition des schèmes de lecture de l'espace politique. Le mot de « populisme », comme la dénonciation de l'UMPS ou l'ambition macronienne d'incarner « et la droite et la gauche » ont contribué à faire advenir un champ politique désormais illisible.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./opinion/">Opinion</a></h3>
<h2 class="title"><a href="https://aoc.media/opinion/2018/12/05/orban-force-universite-a-lexil/">Orbán force notre université à l&rsquo;exil</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/bodnargevafabianikowalski-monterescuaoc-media/"><strong>Judit Bodnar, Dorit Geva, Jean-Louis Fabiani, Alexandra Kowalski et Daniel Monterescu</strong></a></h4>
<a href="https://aoc.media/opinion/2018/12/05/orban-force-universite-a-lexil/" class="desc-4">Fondée et financée par George Soros, la Central European University de Budapest jouait depuis 1991 un rôle majeur dans la formation d'étudiants avancés et, plus largement, dans la vie des idées. Devenue depuis des années la cible de Viktor Orbán, la petite mais prestigieuse institution vient d'annoncer son départ pour Vienne. Réaction de cinq enseignants.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./critique/">Critique</a></h3>
<h2 class="title"><a href="https://aoc.media/critique/2018/12/05/coeur-converti-belle-histoire-de-stefan-hertmans/"><i>Le cœur converti</i> ou la trop belle histoire de Stefan Hertmans</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/juliette-sibon/"><strong>Juliette Sibon</strong></a></h4>
<a href="https://aoc.media/critique/2018/12/05/coeur-converti-belle-histoire-de-stefan-hertmans/" class="desc-4">Avec <i>Le cœur converti</i>, Stefan Hertmans cultive avec talent l'art du croisement des récits, délaissant la linéarité au profit d'une architecture sophistiquée mais la pertinence historique lui fait parfois défaut. Construit comme une plongée littéraire et historique dans le XIe siècle, le roman multiplie les lieux communs et les anachronismes. Une belle histoire, à défaut d'être « vraie ».</a>
</article>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="section list">
<div class="container">
<div class="row row-category">
<div class="col-lg-2">
<div class="date-article">
<!-- date -->
<span>
mardi </span>
04.12 </div>
</div>
<div class="col-lg-9">
<div class="row">
<!-- loop post -->
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./analyse/">Analyse</a></h3>
<h2 class="title"><a href="https://aoc.media/analyse/2018/12/04/face-au-present/">Face au présent</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/francois-hartogaoc-media/"><strong>François Hartog</strong></a></h4>
<a href="https://aoc.media/analyse/2018/12/04/face-au-present/" class="desc-4">Faire face au présent, le diagnostiquer, cest aussi percevoir quil ny a pas qu'un seul présent, le même pour tous, mais des présents. Et, de plus en plus, ces présents sont désaccordés. L'économie est en marche, mais tout le monde ne peut la suivre : les plus pauvres s'appauvrissent ; les ressources terrestres s'épuisent. Cela nourrit des mouvements de repli, de refus, de colère.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./opinion/">Opinion</a></h3>
<h2 class="title"><a href="https://aoc.media/opinion/2018/12/04/menaces-diversite-2-2-humanite-peril/">Menaces sur la diversité (2/2) humanité en péril</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/boris-gresillonaoc-media/"><strong>Boris Grésillon</strong></a></h4>
<a href="https://aoc.media/opinion/2018/12/04/menaces-diversite-2-2-humanite-peril/" class="desc-4">Monopoles et trusts d'un côté ; autocrates et réactionnaires de l'autre : les agents économiques et politiques qui ressurgissent en ce début de XXIe siècle profitent de leur incroyable force de frappe pour uniformiser le monde. Après avoir traité de la biodiversité et des aspects culturels la superstructure , ce second volet d'un diptyque consacré aux menaces qui pèsent sur la diversité s'attaque aux puissances économiques et politiques à l'infrastructure.</a>
</article>
</div>
<div class="col-lg-4">
<article class="article">
<!-- <h3 class="category hidden-category"><a href="javascript:void(0);">Analyse</a></h3> -->
<h3 class="category hidden-category"><a href="https://aoc.media/./critique/">Critique</a></h3>
<h2 class="title"><a href="https://aoc.media/critique/2018/12/04/serie-noire-de-bertrand-schefer-entre-fiction-realite/"><i>Série noire</i> de Bertrand Schefer : entre fiction et réalité</a></h2>
<h4 class="author">par <a href="https://aoc.media/auteur/tanette-sylvie/"><strong>Sylvie Tanette</strong></a></h4>
<a href="https://aoc.media/critique/2018/12/04/serie-noire-de-bertrand-schefer-entre-fiction-realite/" class="desc-4">Dans un passionnant jeu de reconstitution, Bertrand Schefer fait avec <i>Série noire</i> le récit d'un fait divers inspiré dun polar. Manière très originale de sinterroger sur le pouvoir de la littérature comme sur le brouillage des frontières entre réalité et fiction.</a>
</article>
</div>
</div>
</div>
</div>
</div>
</section>
<input type="hidden" class="next_offset_date_home" value="2018-12-04">
</div>
<section class="section list">
<div class="container">
<div class="row">
<div class="col">
<input type="hidden" value="2018-12-04" class="last-day-check">
<div class="content-button">
<a href="javascript:void(0);" class="button-more">Voir plus</a>
</div>
</div>
</div>
</div>
</section>
</main><!-- /.main -->
<!-- /div--><!-- /.content -->
</div><!-- /.wrap -->
<footer class="content-info footer" id="footer">
<div class="container">
<div class="footer-top">
<a href="https://aoc.media"><img
src="https://aoc.media/wp-content/themes/AOC/dist/images/AOC_noir_fond_transparent-300x103.png"
alt="Logo" class="logo-footer"></a>
<p class="p-inscrit">Pour rester informé inscrivez-vous à la newsletter</p>
<span class="trait"></span>
<div role="form" class="form" lang="fr-FR" dir="ltr">
<form action="" method="post" class="posR form-footer insert_user_mc_list_action" data-list-id="112919439a">
<input type="email" name="your-email" value="" size="40" class="email" aria-required="true"
aria-invalid="false" placeholder="Adresse mail" required/>
<input type="submit" value="OK" class="valider "/>
</form>
<p class="pmpro_success ok-nlmessage" style="margin-top: 20px;display:none;">Merci votre inscription à la newsletter est bien prise en compte</p>
<p class="pmpro_message notok-nlmessage" style="margin-top: 20px;display:none;">Un problème est survenue pedant votre inscription à la newsletter.</p>
</div>
</div>
<div class="row footer-bottom">
<div class="col-md-6 col-sm-6 text-left">
<p>Suivez-nous sur les réseaux sociaux</p>
<ul class="social">
<li><a href="https://twitter.com/AOC_media" target="_blank"><i class="fa fa-twitter"></i></a></li>
<li><a href="https://www.instagram.com/aoc.media" target="_blank"><i class="fa fa-instagram"></i></a></li>
<li><a href="https://www.facebook.com/analyse.opinion.critique/" target="_blank"><i class="fa fa-facebook"></i></a></li>
</ul>
</div>
<div class="col-md-6 col-sm-6 text-right">
<p class="p-footer"><a href="https://aoc.media/a-propos-de-aoc/" class="mention">Qui sommes-nous</a> <span class="sep">-</span> <a href="https://aoc.media/mention-legales/" class="mention">Mentions légales</a></p>
<p class="p-footer">Service abonnement : <a href="mailto:contact@aoc.media" class="mention">contact@aoc.media</a></p>
<p class="p-footer">Made with ♥ by <a href="https://www.cosavostra.com/" target="_blank">Cosavostra</a></p>
</div>
</div>
</div>
</footer>
<!-- Modal newsletter-->
<div class="modal fade" id="popup-newsletter" tabindex="-1" role="dialog" aria-labelledby="newsletter"
aria-hidden="true">
<div class="modal-dialog modal-newsletter" role="document">
<div class="modal-content">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">Fermer</span>
</button>
<div class="modal-header">
<a href="https://aoc.media"><img
src="https://aoc.media/wp-content/themes/AOC/dist/images/AOC_noir_fond_transparent-300x103.png"
alt="Logo" class="logo-footer"></a>
</div>
<div class="modal-body">
<p class="p-inscrit">Pour rester informé inscrivez-vous à la newsletter</p>
<span class="trait"></span>
</div>
<div class="modal-footer">
<div role="form" class="form" lang="fr-FR" dir="ltr">
<p class="pmpro_success ok-nlmessage" style="margin-top: 20px;display:none;">Merci votre inscription à la newsletter est bien prise en compte</p>
<p class="pmpro_message notok-nlmessage" style="margin-top: 20px;display:none;">Un problème est survenue pedant votre inscription à la newsletter.</p>
<form action="" method="post" class="posR form-footer insert_user_mc_list_action" data-list-id="112919439a" >
<input type="email" name="your-email" value="" size="40" class="email" aria-required="true" aria-invalid="false" placeholder="Adresse mail" required/>
<input type="submit" value="OK" class="valider"/>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Modal connexion -->
<div class="modal fade" id="popup-connexion" tabindex="-1" role="dialog" aria-labelledby="newsletter"
aria-hidden="true">
<div class="modal-dialog modal-connexion" role="document">
<div class="modal-content">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">Fermer</span>
</button>
<div class="modal-header">
<a href="https://aoc.media"><img
src="https://aoc.media/wp-content/themes/AOC/dist/images/AOC_noir_fond_transparent-300x103.png"
alt="Logo" class="logo-footer"></a>
</div>
<div class="modal-body">
<p class="p-inscrit">Renseignez vos identifiants de connexion AOC.</p>
<span class="trait"></span>
</div>
<div class="modal-footer">
<div role="form" class="form" lang="fr-FR" dir="ltr">
<form action="" method="post" class="form-connexion login" id="form-connexion">
<div>
<label>E-mail</label>
<input type="text" name="nom" value="" size="40" class="nom input" aria-required="true" aria-invalid="false" placeholder="E-mail" required/>
</div>
<div>
<label>Mot de passe</label>
<input type="password" name="password" value="" size="40" class="password input" aria-required="true" aria-invalid="false" placeholder="Mot de passe" required/>
</div>
<input type="submit" value="Connectez-vous" class="valider"/>
</form>
<div class="forgot-password">
<a href="javascript:void(0);" class="forgot-password-modal">Mot de passe oublié ?</a>
</div>
<div class="offres-abonner">
<p>Vous nêtes pas encore abonné</p>
<a href="https://aoc.media/offers/">Abonnez-vous</a><!-- class="inscription-modal" -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal connexion -->
<div class="modal fade" id="popup-password" tabindex="-1" role="dialog" aria-labelledby="newsletter" aria-hidden="true">
<div class="modal-dialog modal-connexion" role="document">
<div class="modal-content">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">Fermer</span>
</button>
<div class="modal-header">
<a href="https://aoc.media"><img
src="https://aoc.media/wp-content/themes/AOC/dist/images/AOC_noir_fond_transparent-300x103.png"
alt="Logo" class="logo-footer"></a>
</div>
<div class="modal-body">
<p class="p-inscrit">Réinitialiser votre mot de passe</p>
<span class="trait"></span>
</div>
<div class="modal-footer">
<div role="form" class="form" lang="fr-FR" dir="ltr">
<div class="top-form">
<p>Indiquez ci-dessous l'e-mail avec lequel vous vous êtes inscrit, vous recevrez un e-mail contenant un lien vous permettant de renseigner votre mot de passe.</p>
</div>
<form action="" method="post" class="form-connexion" id="retrieve_password_form">
<div>
<label>E-mail</label>
<input type="email" name="email" value="" size="40" class="nom input" aria-required="true" aria-invalid="false" />
</div>
<input type="submit" value="Envoyez" class="valider" id="reset_password_button"/>
</form>
<div class="offres-abonner">
<a href="javascript:void(0);" class="back-modal">Revenir à l'étape de connexion</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal inscription -->
<div class="modal fade" id="popup-inscription" tabindex="-1" role="dialog" aria-labelledby="newsletter"
aria-hidden="true">
<div class="modal-dialog modal-inscription" role="document">
<div class="modal-content">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">Fermer</span>
</button>
<div class="modal-header">
<a href="https://aoc.media"><img
src="https://aoc.media/wp-content/themes/AOC/dist/images/AOC_noir_fond_transparent-300x103.png"
alt="Logo" class="logo-footer"></a>
</div>
<div class="modal-body">
<p class="p-inscrit">Créer votre compte AOC.</p>
<span class="trait"></span>
</div>
<div class="modal-footer">
<div role="form" class="form" lang="fr-FR" dir="ltr">
<form action="" id="register_form" method="post" class="form-connexion">
<div>
<label>Votre email</label>
<input type="email" name="email" value="" size="40" class="email" required/>
</div>
<div>
<label>Mot de passe</label>
<input type="password" name="password" value="" size="40" class="password" required/>
</div>
<div>
<label>Confirmer votre mot de passe</label>
<input type="password" name="password" value="" size="40" class="password password-confirm" required/>
</div>
<div class="checkbox">
<input id="conditions-insri" type="checkbox" name="conditions" checked>
<label for="conditions-insri">Je souhaite recevoir la newsletter quotidienne AOC <span>Quotidienne 7j/7</span> </label>
</div>
<input type="submit" value="Inscrivez-vous" class="valider"/>
</form>
<div class="offres-abonner">
<p>Vous avez déjà un compte ?</p>
<a href="#" >Connectez-vous</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- WPS image tracker ne pas enlever -->
<img src="https://tracker.wpserveur.net/piwik.php?idsite=53&rec=1" style="border:0" alt="" />
<!-- WPS image tracker ne pas enlever -->
<!-- Memberships powered by Paid Memberships Pro v1.9.5.5.
-->
<script type='text/javascript'>
/* <![CDATA[ */
var ctcc_vars = {"expiry":"395","method":"","version":"1"};
/* ]]> */
</script>
<script type='text/javascript' src='https://aoc.media/wp-content/plugins/uk-cookie-consent/assets/js/uk-cookie-consent-js.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-content/themes/AOC/dist/scripts/hammer-8ea71ffb47.min.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-content/themes/AOC/dist/scripts/modernizr-1279678b7d.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-content/themes/AOC/dist/scripts/chosen-37d25c1810.jquery.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-content/themes/AOC/dist/scripts/jquery-0e88deb2d9.mCustomScrollbar.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-content/themes/AOC/dist/scripts/jquery-1ebd4d58ac.collapser.js'></script>
<script type='text/javascript'>
/* <![CDATA[ */
var aoc_ajax_object = {"ajax_url":"https:\/\/aoc.media\/wp-admin\/admin-ajax.php","security":"c506c1b8bc","home_url":"https:\/\/aoc.media\/","current_url":"https:\/\/aoc.media"};
/* ]]> */
</script>
<script type='text/javascript' src='https://aoc.media/wp-content/themes/AOC/dist/scripts/main-792c660dcc.js'></script>
<script type='text/javascript' src='https://aoc.media/wp-includes/js/wp-embed.min.js'></script>
<script type="text/javascript">
jQuery(document).ready(function($){
if(!catapultReadCookie("catAccCookies")){ // If the cookie has not been set then show the bar
$("html").addClass("has-cookie-bar");
$("html").addClass("cookie-bar-bottom-bar");
$("html").addClass("cookie-bar-bar");
}
});
</script>
<div id="catapult-cookie-bar" class=""><div class="ctcc-inner custom-cookie"><span class="ctcc-left-side"> En poursuivant votre navigation sur ce site, vous acceptez lutilisation de cookies de mesures daudience et de partage sur les réseaux sociaux. <a class="ctcc-more-info-link" tabindex=0 target="_blank" href="https://aoc.media/conditions-generales-de-vente/">En savoir plus.</a></span><span class="ctcc-right-side"><button id="catapultCookie" tabindex=0 onclick="catapultAcceptCookies();">OK</button></span></div><!-- custom wrapper class --></div><!-- #catapult-cookie-bar -->
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="verification" content="69d69162a5afacbd95ee9912f6bc4f42" />
<title>Interface de connexion</title>
<link href="/Images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="/Content/css?v=_NupXuE9HfvLdJgFWXCdp0x9FqlPxy6j67waGCSZRRw1" rel="stylesheet"/>
<script src="/bundles/modernizr?v=w9fZKPSiHtN4N4FRqV7jn-3kGoQY5hHpkwFv5TfMrus1"></script>
<script src="/bundles/globalScripts?v=lmrVltPtqGY5X7j2oRzlv_orPqh-YxHIU3O-xHw8c9U1"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<div class="logo-left-header">
<a href="/" class="logo_header navbar-brand" data-ajax="false"></a>
</div>
</div>
</div>
</div>
<div class="container body-content" style="">
<section id="bloc_identification">
<div id="classic_auth_part" class="mini_bloc_white">
<div class="info_head_circle">
<i class="icon-user"></i>
</div>
<h2>Connectez-vous via vos identifiants</h2>
<form action="/Account/Login" data-ajax="false" id="formLogin" method="post"><input name="__RequestVerificationToken" type="hidden" value="s6x2QcnQDUL92mkKSi_JuUBXcgUYx_Plf-KyQ2eJypKAjQZIeTvaFHOsfEdTrcSXt3dt2CW39V7r9V16LUtvjszodAU1" /><span class="field-validation-valid" data-valmsg-for="Error" data-valmsg-replace="true"></span> <div class="input-text-auth">
<i class="icon-user"></i>
<input class="txtb-auth" data-val="true" data-val-required="Veuillez saisir votre nom d&#39;utilisateur" id="UserName" name="UserName" placeholder="Pseudonyme ou email" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
</div>
<div class="input-text-auth">
<i class="icon-lock"></i>
<input class="txtb-auth" data-val="true" data-val-required="Veuillez saisir votre mot de passe" id="Password" name="Password" placeholder="Mot de passe" type="password" />
<span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span>
</div>
<input type="hidden" value="True" name="RememberMe" id="RememberMe" />
<input type="hidden" name="returnUrl" />
<div id="bloc_button_login">
<ul>
<li>
<a href="/pass-request" id="forgotten_passwd">
Mot de passe oublié ?
</a>
</li>
<li>
<a href="/inscription" id="not_registered">
Pas encore inscrit ?
</a>
</li>
</ul>
<button type="submit" class="btn_connexion bg-green-button float-right" id="register_options"><i class='icon-ok-circled'></i>Connexion</button>
</div>
</form></div><div id="social_auth_part">
<div class="info_head_circle" id="social-icone">
<span>OU</span>
</div>
<form action="/Account/ExternalLogin" id="formOAuth" method="post"><input name="__RequestVerificationToken" type="hidden" value="taWXtvSh7Qj6DnyDqT424oTuCABOUJZ-sUisbRvebVLN7eK_q1v5_2pPw41TQh1RvFlTurJozjJ-vA_OZzz-i6qWCTk1" /> <div id="socialLoginList" style="">
<p>
<input type="radio" name="provider" value="Facebook" checked=checked class="radio_provider" />
<button type="submit" class="btn btn-default btn_login_social " id="button_Facebook_login" name="provider" value="Facebook" title="Identifiez-vous avec votre compte Facebook"></button>
<input type="radio" name="provider" value="Google" checked=checked class="radio_provider" />
<button type="submit" class="btn btn-default btn_login_social " id="button_Google_login" name="provider" value="Google" title="Identifiez-vous avec votre compte Google"></button>
<input type="radio" name="provider" value="Twitter" checked=checked class="radio_provider" />
<button type="submit" class="btn btn-default btn_login_social " id="button_Twitter_login" name="provider" value="Twitter" title="Identifiez-vous avec votre compte Twitter"></button>
<input type="hidden" name="returnUrl" />
</p>
</div>
</form>
</div>
</section>
</div>
<footer>
<div id="footer_content" class="container">
<div id="left_footer">
<div id="payments_dispo">
</div>
</div>
<div id="right_footer">
<div id="bloc_copyright_footer">
<p>Copyright © 2003 - 2018 - INpact Mediagroup SARL de presse. N° de CPPAP 0321 Z 92244 </p>
<p>Marque déposée. Tous droits réservés. Design par <a href="http://www.btoweb-solutions.fr/" target="_blank" class="" style="color: #DFDFDF !important;">BToWeb</a></p>
</div>
</div>
</div>
</footer>
<script src="/bundles/jqueryval?v=V5eNwMVb1HNdAHpjPz3kqQOA3wh_pjGv4x8HHkR6-1o1"></script>
<script src="/bundles/bootstrap?v=NzP9D5jO6GVMzY8_4Kfk811W0VrrhYdC5doOJzjbWJc1"></script>
<script src="/bundles/login?v=hdks_e0EWY6hlKo9UmjOqy_9MVO7J0BFfnTiJl0qAfA1"></script>
<script src="/bundles/jqueryval?v=V5eNwMVb1HNdAHpjPz3kqQOA3wh_pjGv4x8HHkR6-1o1"></script>
</body>
</html>