Skip to content

Add Edit In Place functionnality #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions Controller/EditInPlaceController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/*
* This file is part of the PHP Translation package.
*
* (c) PHP Translation team <tobias.nyholm@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Translation\Bundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Translation\Bundle\Exception\MessageValidationException;
use Translation\Bundle\Model\EditInPlaceMessage;
use Translation\Common\Model\Message;

/**
* @author Damien Alexandre <dalexandre@jolicode.com>
*/
class EditInPlaceController extends Controller
{
/**
* @param Request $request
* @param string $configName
* @param string $locale
*
* @return Response
*/
public function editAction(Request $request, $configName, $locale)
{
try {
$messages = $this->getMessages($request, ['Edit']);
} catch (MessageValidationException $e) {
return new Response($e->getMessage(), Response::HTTP_BAD_REQUEST);
}

foreach ($messages as $message) {
$this->get('php_translation.storage.'.$configName)->update(
new Message($message->getKey(), $message->getDomain(), $locale, $message->getMessage())
);
}

return new Response();
}

/**
* Get and validate messages from the request.
*
* @param Request $request
* @param array $validationGroups
*
* @return EditInPlaceMessage[]
*
* @throws MessageValidationException
*/
private function getMessages(Request $request, array $validationGroups = [])
{
$json = $request->getContent();
$data = json_decode($json, true);
$messages = [];
$validator = $this->get('validator');

foreach ($data as $key => $value) {
list($domain, $translationKey) = explode('|', $key);

$message = new EditInPlaceMessage();
$message->setKey($translationKey);
$message->setMessage($value);
$message->setDomain($domain);

$errors = $validator->validate($message, null, $validationGroups);
if (count($errors) > 0) {
throw MessageValidationException::create();
}

$messages[] = $message;
}

return $messages;
}
}
37 changes: 37 additions & 0 deletions DependencyInjection/CompilerPass/EditInPlacePass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the PHP Translation package.
*
* (c) PHP Translation team <tobias.nyholm@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Translation\Bundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

/**
* @author Damien Alexandre <dalexandre@jolicode.com>
*/
class EditInPlacePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
/* @var Definition $def */
if (!$container->hasDefinition('php_translator.edit_in_place.xtrans_html_translator')) {
return;
}

// Replace the Twig Translator by a custom HTML one
$container->getDefinition('twig.extension.trans')->replaceArgument(
0,
new Reference('php_translator.edit_in_place.xtrans_html_translator')
);
}
}
7 changes: 7 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ public function getConfigTreeBuilder()
->booleanNode('allow_add')->defaultTrue()->end()
->end()
->end()
->arrayNode('edit_in_place')
->canBeEnabled()
->children()
->scalarNode('config_name')->defaultValue('default')->end()
->scalarNode('activator')->cannotBeEmpty()->defaultValue('php_translation.edit_in_place.activator')->end()
->end()
->end()
->scalarNode('http_client')->cannotBeEmpty()->defaultValue('httplug.client')->end()
->scalarNode('message_factory')->cannotBeEmpty()->defaultValue('httplug.message_factory')->end()
->end();
Expand Down
24 changes: 24 additions & 0 deletions DependencyInjection/TranslationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
Expand Down Expand Up @@ -55,6 +56,11 @@ public function load(array $configs, ContainerBuilder $container)
$this->enableSymfonyProfiler($container, $config);
}

if ($config['edit_in_place']['enabled']) {
$loader->load('edit_in_place.yml');
$this->enableEditInPlace($container, $config);
}

if ($config['fallback_translation']['enabled']) {
$loader->load('auto_translation.yml');
$this->enableFallbackAutoTranslator($container, $config);
Expand Down Expand Up @@ -104,6 +110,24 @@ private function enableWebUi(ContainerBuilder $container, $config)
{
}

private function enableEditInPlace(ContainerBuilder $container, $config)
{
$name = $config['edit_in_place']['config_name'];

if ($name !== 'default' and !isset($config['configs'][$name])) {
throw new InvalidArgumentException(sprintf('There is no config named "%s".', $name));
}

$activatorRef = new Reference($config['edit_in_place']['activator']);

$def = $container->getDefinition('php_translation.edit_in_place.response_listener');
$def->replaceArgument(0, $activatorRef);
$def->replaceArgument(3, $name);

$def = $container->getDefinition('php_translator.edit_in_place.xtrans_html_translator');
$def->replaceArgument(1, $activatorRef);
}

private function enableSymfonyProfiler(ContainerBuilder $container, $config)
{
$container->setParameter('php_translation.toolbar.allow_edit', $config['symfony_profiler']['allow_edit']);
Expand Down
65 changes: 65 additions & 0 deletions EditInPlace/Activator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/*
* This file is part of the PHP Translation package.
*
* (c) PHP Translation team <tobias.nyholm@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Translation\Bundle\EditInPlace;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

/**
* Default Activator implementation.
*
* @author Damien Alexandre <dalexandre@jolicode.com>
*/
class Activator implements ActivatorInterface
{
const KEY = 'translation_bundle.edit_in_place.enabled';

/**
* @var Session
*/
private $session;

public function __construct(Session $session)
{
$this->session = $session;
}

/**
* Enable the Edit In Place mode.
*/
public function activate()
{
$this->session->set(self::KEY, true);
}

/**
* Disable the Edit In Place mode.
*/
public function deactivate()
{
$this->session->remove(self::KEY);
}

/**
* {@inheritdoc}
*
* @todo Cache this call result for performance?
*/
public function checkRequest(Request $request = null)
{
if (!$this->session->has(self::KEY)) {
return false;
}

return $this->session->has(self::KEY);
}
}
29 changes: 29 additions & 0 deletions EditInPlace/ActivatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the PHP Translation package.
*
* (c) PHP Translation team <tobias.nyholm@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Translation\Bundle\EditInPlace;

use Symfony\Component\HttpFoundation\Request;

/**
* @author Damien Alexandre <dalexandre@jolicode.com>
*/
interface ActivatorInterface
{
/**
* Tells if the Edit In Place mode is enabled for this request.
*
* @param Request|null $request
*
* @return bool
*/
public function checkRequest(Request $request = null);
}
102 changes: 102 additions & 0 deletions EditInPlace/ResponseListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

/*
* This file is part of the PHP Translation package.
*
* (c) PHP Translation team <tobias.nyholm@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Translation\Bundle\EditInPlace;

use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\Routing\Router;

/**
* Adds Javascript/CSS files to the Response if the Activator returns true.
*
* @author Damien Alexandre <dalexandre@jolicode.com>
*/
class ResponseListener
{
const HTML = <<<'HTML'
<!-- TranslationBundle -->
<link rel="stylesheet" type="text/css" href="%s">

<script type="text/javascript" src="%s"></script>
<script type="text/javascript" src="%s"></script>

<script type="text/javascript">
window.onload = function() {
TranslationBundleEditInPlace("%s");
}
</script>
<!-- /TranslationBundle -->
HTML;

/**
* @var ActivatorInterface
*/
private $activator;

/**
* @var Router
*/
private $router;

/**
* @var Packages
*/
private $packages;

/**
* @var string
*/
private $configName;

public function __construct(ActivatorInterface $activator, Router $router, Packages $packages, $configName = 'default')
{
$this->activator = $activator;
$this->router = $router;
$this->packages = $packages;
$this->configName = $configName;
}

public function onKernelResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();

if ($this->activator->checkRequest($request)) {
$content = $event->getResponse()->getContent();

// Clean the content for malformed tags in attributes or encoded tags
$content = preg_replace("@=\\s*[\"']\\s*(<x-trans.+<\\/x-trans>)\\s*[\"']@mi", "=\"🚫 Can't be translated here. 🚫\"", $content);
$content = preg_replace('@&lt;x-trans.+data-key=&quot;([^&]+)&quot;.+&lt;\\/x-trans&gt;@mi', '🚫 $1 🚫', $content);

$html = sprintf(
self::HTML,
$this->packages->getUrl('bundles/translation/css/content-tools.min.css'),
$this->packages->getUrl('bundles/translation/js/content-tools.min.js'),
$this->packages->getUrl('bundles/translation/js/editInPlace.js'),

$this->router->generate('translation_edit_in_place_update', [
'configName' => $this->configName,
'locale' => $event->getRequest()->getLocale(),
])
);
$content = str_replace('</body>', $html."\n".'</body>', $content);

$response = $event->getResponse();

// Remove the cache because we do not want the modified page to be cached
$response->headers->set('cache-control', 'no-cache, no-store, must-revalidate');
$response->headers->set('pragma', 'no-cache');
$response->headers->set('expires', '0');

$event->getResponse()->setContent($content);
}
}
}
Loading