mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 22:53:49 +00:00
Merge branch 'master' into _bootstrap_modal
This commit is contained in:
@@ -14,6 +14,7 @@ use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchy;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@@ -43,6 +44,11 @@ class PermissionsGroupController extends AbstractController
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @var ValidatorInterface
|
||||
*/
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* PermissionsGroupController constructor.
|
||||
*
|
||||
@@ -50,18 +56,21 @@ class PermissionsGroupController extends AbstractController
|
||||
* @param RoleProvider $roleProvider
|
||||
* @param RoleHierarchy $roleHierarchy
|
||||
* @param TranslatorInterface $translator
|
||||
* @param ValidatorInterface $validator
|
||||
*/
|
||||
public function __construct(
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
RoleProvider $roleProvider,
|
||||
RoleHierarchy $roleHierarchy,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
ValidatorInterface $validator
|
||||
)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->roleProvider = $roleProvider;
|
||||
$this->roleHierarchy = $roleHierarchy;
|
||||
$this->translator = $translator;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,7 +461,7 @@ class PermissionsGroupController extends AbstractController
|
||||
);
|
||||
|
||||
$permissionsGroup->addRoleScope($roleScope);
|
||||
$violations = $this->get('validator')->validate($permissionsGroup);
|
||||
$violations = $this->validator->validate($permissionsGroup);
|
||||
|
||||
if ($violations->count() === 0) {
|
||||
$em->flush();
|
||||
|
@@ -11,6 +11,7 @@ use Chill\MainBundle\Form\UserType;
|
||||
use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Form\Type\ComposedGroupCenterType;
|
||||
use Chill\MainBundle\Form\UserPasswordType;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,14 +29,21 @@ class UserController extends AbstractController
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var ValidatorInterface
|
||||
*/
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* UserController constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
* @param ValidatorInterface $validator
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger)
|
||||
public function __construct(LoggerInterface $logger, ValidatorInterface $validator)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +266,7 @@ class UserController extends AbstractController
|
||||
$form[self::FORM_GROUP_CENTER_COMPOSED]->getData());
|
||||
$user->addGroupCenter($groupCenter);
|
||||
|
||||
if ($this->get('validator')->validate($user)->count() === 0) {
|
||||
if ($this->validator->validate($user)->count() === 0) {
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans('The '
|
||||
@@ -267,7 +275,7 @@ class UserController extends AbstractController
|
||||
return $this->redirect($this->generateUrl('admin_user_edit',
|
||||
array('id' => $uid)));
|
||||
} else {
|
||||
foreach($this->get('validator')->validate($user) as $error)
|
||||
foreach($this->validator->validate($user) as $error)
|
||||
$this->addFlash('error', $error->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -380,7 +388,7 @@ class UserController extends AbstractController
|
||||
|
||||
// logging for prod
|
||||
$this->logger->info('update password for an user', [
|
||||
'by' => $this->getUser()->getUsername(),
|
||||
'by' => $this->getUser()->getUsername(),
|
||||
'user' => $user->getUsername()
|
||||
]);
|
||||
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Form\Extension;
|
||||
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class CKEditorTypeExtension
|
||||
*
|
||||
* @package Chill\MainBundle\Form\Extension
|
||||
* @author Mathieu Jaumotte mathieu.jaumotte@champs-libres.coop
|
||||
*/
|
||||
class CKEditorExtension extends AbstractTypeExtension
|
||||
{
|
||||
|
||||
/**
|
||||
* Return the class of the Textarea Type being extended.
|
||||
* @return iterable
|
||||
*/
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [TextareaType::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'attr' => [
|
||||
'class' => 'ckeditor snippet-markdown'
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
57
src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php
Normal file
57
src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2017 Champs Libres Cooperative <info@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
|
||||
/**
|
||||
* Create a Textarea
|
||||
*
|
||||
* By default, add a WYSIWYG editor.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* * `disable_editor`: set true to disable editor
|
||||
*
|
||||
*/
|
||||
final class ChillTextareaType extends AbstractType
|
||||
{
|
||||
public function getParent()
|
||||
{
|
||||
return TextareaType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefined('disable_editor')
|
||||
->setDefault('disable_editor', false)
|
||||
->setAllowedTypes('disable_editor', 'bool');
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
if (!$options['disable_editor']) {
|
||||
$view->vars['attr']['ckeditor'] = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,7 +20,7 @@ namespace Chill\MainBundle\Form\Type;
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
@@ -44,8 +44,8 @@ class CommentType extends AbstractType
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('comment', TextareaType::class, [
|
||||
|
||||
->add('comment', ChillTextareaType::class, [
|
||||
'disable_editor' => $options['disable_editor']
|
||||
])
|
||||
;
|
||||
|
||||
@@ -72,8 +72,12 @@ class CommentType extends AbstractType
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => CommentEmbeddable::class,
|
||||
]);
|
||||
$resolver
|
||||
->setDefined('disable_editor')
|
||||
->setAllowedTypes('disable_editor', 'bool')
|
||||
->setDefaults([
|
||||
'data_class' => CommentEmbeddable::class,
|
||||
'disable_editor' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -26,146 +26,168 @@ use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* Helper to some task linked to phonenumber.
|
||||
*
|
||||
* Currently, only Twilio is supported (https://www.twilio.com/lookup). A method
|
||||
*
|
||||
* Currently, only Twilio is supported (https://www.twilio.com/lookup). A method
|
||||
* allow to check if the helper is configured for validation. This should be used
|
||||
* before doing some validation.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
class PhonenumberHelper
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var Client
|
||||
* Twilio client
|
||||
*/
|
||||
protected $twilioClient;
|
||||
protected Client $twilioClient;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var LoggerInterface
|
||||
* TRUE if the client is properly configured
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var CacheItemPoolInterface
|
||||
*/
|
||||
protected $cachePool;
|
||||
|
||||
protected bool $isConfigured = false;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
protected CacheItemPoolInterface $cachePool;
|
||||
|
||||
const LOOKUP_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s';
|
||||
const FORMAT_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s';
|
||||
|
||||
|
||||
|
||||
public function __construct(
|
||||
CacheItemPoolInterface $cachePool,
|
||||
$config,
|
||||
$config,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->cachePool = $cachePool;
|
||||
|
||||
if (\array_key_exists('twilio_sid', $config)
|
||||
|
||||
if (\array_key_exists('twilio_sid', $config)
|
||||
&& !empty($config['twilio_sid'])
|
||||
&& \array_key_exists('twilio_secret', $config)
|
||||
&& !empty($config['twilio_secret'])) {
|
||||
|
||||
&& strlen($config['twilio_sid']) > 2
|
||||
&& \array_key_exists('twilio_secret', $config)
|
||||
&& !empty($config['twilio_secret'])
|
||||
&& strlen($config['twilio_secret']) > 2
|
||||
) {
|
||||
|
||||
$this->twilioClient = new Client([
|
||||
'auth' => [ $config['twilio_sid'], $config['twilio_secret'] ]
|
||||
]);
|
||||
}
|
||||
$this->isConfigured = TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the validation is configured and available.
|
||||
*
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPhonenumberValidationConfigured() : bool
|
||||
{
|
||||
return NULL !== $this->twilioClient;
|
||||
return $this->isConfigured;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* REturn true if the phoennumber is a mobile phone. Return always false
|
||||
* REturn true if the phoennumber is a mobile phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*
|
||||
*
|
||||
* @param string $phonenumber
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidPhonenumberMobile($phonenumber) : bool
|
||||
{
|
||||
$validation = $this->performTwilioLookup($phonenumber);
|
||||
|
||||
if (NULL === $validation) {
|
||||
return false;
|
||||
if (FALSE === $this->isPhonenumberValidationConfigured()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$validation = $this->performTwilioLookup($phonenumber);
|
||||
|
||||
if (NULL === $validation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $validation === 'mobile';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the phonenumber is a landline or voip phone. Return always false
|
||||
* Return true if the phonenumber is a landline or voip phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*
|
||||
*
|
||||
* @param string $phonenumber
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidPhonenumberLandOrVoip($phonenumber) : bool
|
||||
{
|
||||
$validation = $this->performTwilioLookup($phonenumber);
|
||||
|
||||
if (NULL === $validation) {
|
||||
return false;
|
||||
if (FALSE === $this->isPhonenumberValidationConfigured()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$validation = $this->performTwilioLookup($phonenumber);
|
||||
|
||||
if (NULL === $validation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return \in_array($validation, [ 'landline', 'voip' ]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the phonenumber is a landline or voip phone. Return always false
|
||||
* Return true if the phonenumber is a landline or voip phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*
|
||||
*
|
||||
* @param string $phonenumber
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidPhonenumberAny($phonenumber) : bool
|
||||
{
|
||||
if (FALSE === $this->isPhonenumberValidationConfigured()) {
|
||||
return true;
|
||||
}
|
||||
$validation = $this->performTwilioLookup($phonenumber);
|
||||
|
||||
;
|
||||
if (NULL === $validation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return \in_array($validation, [ 'landline', 'voip', 'mobile' ]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get type (mobile, landline, ...) for phone number
|
||||
*
|
||||
* @param string $phonenumber
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(string $phonenumber): string
|
||||
{
|
||||
return $this->performTwilioLookup($phonenumber) ?? 'unknown';
|
||||
}
|
||||
|
||||
public function format($phonenumber)
|
||||
{
|
||||
return $this->performTwilioFormat($phonenumber);
|
||||
}
|
||||
|
||||
|
||||
protected function performTwilioFormat($phonenumber)
|
||||
{
|
||||
if (FALSE === $this->isPhonenumberValidationConfigured()) {
|
||||
return $phonenumber;
|
||||
}
|
||||
|
||||
|
||||
// filter only number
|
||||
$filtered = \preg_replace("/[^0-9]/", "", $phonenumber);
|
||||
|
||||
|
||||
$item = $this->cachePool->getItem('pnum_format_nat_'.$filtered);
|
||||
|
||||
|
||||
if ($item->isHit()) {
|
||||
return $item->get();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$response = $this->twilioClient->get(sprintf(self::FORMAT_URI, '+'.$filtered), [
|
||||
'http_errors' => true,
|
||||
]);
|
||||
|
||||
|
||||
} catch (ClientException $e) {
|
||||
$response = $e->getResponse();
|
||||
$this->logger->error("[phonenumber helper] Could not format number "
|
||||
@@ -174,7 +196,7 @@ class PhonenumberHelper
|
||||
"status_code" => $response->getStatusCode(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
|
||||
|
||||
return $phonenumber;
|
||||
} catch (ServerException $e) {
|
||||
$response = $e->getResponse();
|
||||
@@ -184,7 +206,7 @@ class PhonenumberHelper
|
||||
"status_code" => $response->getStatusCode(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
|
||||
|
||||
return null;
|
||||
} catch (ConnectException $e) {
|
||||
$this->logger->error("[phonenumber helper] Could not format number "
|
||||
@@ -192,38 +214,38 @@ class PhonenumberHelper
|
||||
"message" => $e->getMessage(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$format = \json_decode($response->getBody())->national_format;
|
||||
|
||||
|
||||
$item
|
||||
->set($format)
|
||||
// expires after 3d
|
||||
->expiresAfter(3600 * 24 * 3)
|
||||
;
|
||||
|
||||
|
||||
$this->cachePool->save($item);
|
||||
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
|
||||
protected function performTwilioLookup($phonenumber)
|
||||
{
|
||||
if (FALSE === $this->isPhonenumberValidationConfigured()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// filter only number
|
||||
$filtered = \preg_replace("/[^0-9]/", "", $phonenumber);
|
||||
|
||||
|
||||
$item = $this->cachePool->getItem('pnum_'.$filtered);
|
||||
|
||||
|
||||
if ($item->isHit()) {
|
||||
return $item->get();
|
||||
//return $item->get();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$response = $this->twilioClient->get(sprintf(self::LOOKUP_URI, '+'.$filtered), [
|
||||
'http_errors' => true,
|
||||
@@ -241,7 +263,7 @@ class PhonenumberHelper
|
||||
"status_code" => $response->getStatusCode(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
|
||||
|
||||
return null;
|
||||
} catch (ConnectException $e) {
|
||||
$this->logger->error("[phonenumber helper] Could not format number "
|
||||
@@ -249,20 +271,20 @@ class PhonenumberHelper
|
||||
"message" => $e->getMessage(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$validation = \json_decode($response->getBody())->carrier->type;
|
||||
|
||||
|
||||
$item
|
||||
->set($validation)
|
||||
// expires after 12h
|
||||
->expiresAfter(3600 * 12)
|
||||
;
|
||||
|
||||
|
||||
$this->cachePool->save($item);
|
||||
|
||||
|
||||
return $validation;
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ require('./modules/breadcrumb/index.js');
|
||||
require('./modules/download-report/index.js');
|
||||
require('./modules/select_interactive_loading/index.js');
|
||||
require('./modules/export-list/export-list.scss');
|
||||
require('./modules/entity/index.js');
|
||||
|
||||
/*
|
||||
* load img
|
||||
|
@@ -1,39 +1,24 @@
|
||||
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
|
||||
import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials';
|
||||
import MarkdownPlugin from '@ckeditor/ckeditor5-markdown-gfm/src/markdown';
|
||||
//import UploadAdapterPlugin from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
|
||||
//import AutoformatPlugin from '@ckeditor/ckeditor5-autoformat/src/autoformat';
|
||||
import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold';
|
||||
import ItalicPlugin from '@ckeditor/ckeditor5-basic-styles/src/italic';
|
||||
import BlockQuotePlugin from '@ckeditor/ckeditor5-block-quote/src/blockquote';
|
||||
//import EasyImagePlugin from '@ckeditor/ckeditor5-easy-image/src/easyimage';
|
||||
import HeadingPlugin from '@ckeditor/ckeditor5-heading/src/heading';
|
||||
//import ImagePlugin from '@ckeditor/ckeditor5-image/src/image';
|
||||
//import ImageCaptionPlugin from '@ckeditor/ckeditor5-image/src/imagecaption';
|
||||
//import ImageStylePlugin from '@ckeditor/ckeditor5-image/src/imagestyle';
|
||||
//import ImageToolbarPlugin from '@ckeditor/ckeditor5-image/src/imagetoolbar';
|
||||
//import ImageUploadPlugin from '@ckeditor/ckeditor5-image/src/imageupload';
|
||||
import LinkPlugin from '@ckeditor/ckeditor5-link/src/link';
|
||||
import ListPlugin from '@ckeditor/ckeditor5-list/src/list';
|
||||
import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph';
|
||||
import "./index.scss";
|
||||
|
||||
export default class ClassicEditor extends ClassicEditorBase {}
|
||||
|
||||
ClassicEditor.builtinPlugins = [
|
||||
EssentialsPlugin,
|
||||
MarkdownPlugin,
|
||||
// UploadAdapterPlugin,
|
||||
// AutoformatPlugin,
|
||||
BoldPlugin,
|
||||
ItalicPlugin,
|
||||
BlockQuotePlugin,
|
||||
// EasyImagePlugin,
|
||||
HeadingPlugin,
|
||||
// ImagePlugin,
|
||||
// ImageCaptionPlugin,
|
||||
// ImageStylePlugin,
|
||||
// ImageToolbarPlugin,
|
||||
// ImageUploadPlugin,
|
||||
LinkPlugin,
|
||||
ListPlugin,
|
||||
ParagraphPlugin
|
||||
@@ -49,27 +34,19 @@ ClassicEditor.defaultConfig = {
|
||||
'link',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
// 'uploadImage',
|
||||
'blockQuote',
|
||||
'undo',
|
||||
'redo'
|
||||
]
|
||||
},
|
||||
// image: {
|
||||
// toolbar: [
|
||||
// 'imageStyle:full',
|
||||
// 'imageStyle:side',
|
||||
// '|',
|
||||
// 'imageTextAlternative'
|
||||
// ]
|
||||
// },
|
||||
language: 'fr'
|
||||
};
|
||||
|
||||
|
||||
let Fields = [];
|
||||
Fields.push.apply(Fields, document.querySelectorAll('textarea.ckeditor'));
|
||||
Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));
|
||||
Fields.push.apply(Fields, document.querySelectorAll('textarea[ckeditor]'));
|
||||
// enable for custom fields
|
||||
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));
|
||||
|
||||
Fields.forEach(function(field) {
|
||||
ClassicEditor
|
||||
@@ -81,4 +58,4 @@ Fields.forEach(function(field) {
|
||||
console.error( error.stack );
|
||||
})
|
||||
;
|
||||
})
|
||||
});
|
@@ -0,0 +1,8 @@
|
||||
// set min height for ckeditor
|
||||
.ck-editor__editable {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.ck-editor.ck-reset {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
.chill-entity__comment-embeddable {
|
||||
.chill-entity__comment-embeddable__metadata {
|
||||
font-size: smaller;
|
||||
color: var(--chill-light-gray);
|
||||
}
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
// css classes to render entities
|
||||
require('./comment_embeddable.scss');
|
@@ -157,16 +157,27 @@ dl.chill_view_data {
|
||||
}
|
||||
|
||||
|
||||
blockquote.chill-user-quote {
|
||||
blockquote.chill-user-quote,
|
||||
div.chill-user-quote {
|
||||
border-left: 10px solid $chill-yellow;
|
||||
margin: 1.5em 10px;
|
||||
padding: 0.5em 10px;
|
||||
quotes: "\201C""\201D""\2018""\2019";
|
||||
background-color: $chill-llight-gray;
|
||||
|
||||
|
||||
p { display: inline; }
|
||||
|
||||
|
||||
blockquote {
|
||||
margin: 1.5em 10px;
|
||||
padding: 0.5em 10px;
|
||||
}
|
||||
|
||||
blockquote:before {
|
||||
color: #ccc;
|
||||
content: open-quote;
|
||||
font-size: 4em;
|
||||
line-height: 0.1em;
|
||||
margin-right: 0.25em;
|
||||
vertical-align: -0.4em;
|
||||
}
|
||||
}
|
||||
|
||||
.chill-no-data-statement {
|
||||
|
@@ -148,7 +148,6 @@ form {
|
||||
li {
|
||||
label {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,36 @@
|
||||
{{ opening_box|raw }}
|
||||
<div class="">
|
||||
<div class="comment-embeddable_comment">
|
||||
|
||||
{%- if options['limit_lines'] is not null -%}
|
||||
{% set content = comment.comment|split('\n')|slice(0, options['limit_lines'])|join('\n') %}
|
||||
{%- else -%}
|
||||
{% set content = comment.comment %}
|
||||
{%- endif -%}
|
||||
|
||||
<blockquote class="chill-user-quote">
|
||||
{% if options['disable_markdown'] %}
|
||||
{{ content|nl2br }}
|
||||
{% else %}
|
||||
{{ content|chill_markdown_to_html }}
|
||||
{% endif %}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
{% if options['metadata'] %}
|
||||
<div class="chill-entity__comment-embeddable__metadata">
|
||||
{% if user is not empty %}
|
||||
<span class="chill-entity__comment-embeddable__user">
|
||||
{{ 'Last updated by'| trans }} {{ user|chill_entity_render_box(options['user']) }}
|
||||
</span>';
|
||||
{% endif %}
|
||||
|
||||
{% if comment.date is not empty %}
|
||||
<span class="chill-entity__comment-embeddable__date">
|
||||
{% if user is empty %}{{ 'Last updated on'|trans ~ ' ' }}{% else %}{{ 'on'|trans ~ ' ' }}{% endif %}
|
||||
{{ comment.date|format_datetime("medium", "short") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ closing_box|raw }}
|
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2021 Champs Libres Cooperative <info@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\MainBundle\Templating;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
use Parsedown;
|
||||
|
||||
/**
|
||||
* Render markdown
|
||||
*/
|
||||
final class ChillMarkdownRenderExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* @var Parsedown
|
||||
*/
|
||||
protected $parsedown;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->parsedown = new Parsedown();
|
||||
$this->parsedown->setSafeMode(true);
|
||||
}
|
||||
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('chill_markdown_to_html', [$this, 'renderMarkdownToHtml'], [
|
||||
'is_safe' => [ 'html' ]
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
public function renderMarkdownToHtml(string $var): string
|
||||
{
|
||||
return $this->parsedown->parse($var);
|
||||
}
|
||||
}
|
@@ -21,9 +21,9 @@
|
||||
namespace Chill\MainBundle\Templating\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
use Chill\MainBundle\Templating\Entity\AbstractChillEntityRender;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
class CommentRender extends AbstractChillEntityRender
|
||||
{
|
||||
@@ -31,10 +31,19 @@ class CommentRender extends AbstractChillEntityRender
|
||||
* @var \Chill\MainBundle\Repository\UserRepository
|
||||
*/
|
||||
private $userRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var EngineInterface
|
||||
*/
|
||||
private $engine;
|
||||
|
||||
public function __construct(UserRepository $userRepository)
|
||||
{
|
||||
public function __construct(
|
||||
UserRepository $userRepository,
|
||||
EngineInterface $engine
|
||||
) {
|
||||
$this->userRepository = $userRepository;
|
||||
$this->engine = $engine;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,35 +54,29 @@ class CommentRender extends AbstractChillEntityRender
|
||||
*/
|
||||
public function renderBox($entity, array $options): string
|
||||
{
|
||||
$username = '';
|
||||
|
||||
// default options
|
||||
$options = \array_merge([
|
||||
'user' => [],
|
||||
'disable_markdown' => false,
|
||||
'limit_lines' => null,
|
||||
'metadata' => true
|
||||
], $options);
|
||||
|
||||
if ($entity->getUserId()) {
|
||||
$user = $this->userRepository->find($entity->getUserId());
|
||||
if ($user instanceof User) {
|
||||
$username = $user->getUsername();
|
||||
}
|
||||
}
|
||||
|
||||
$str = $this->getDefaultOpeningBox('comment-embeddable').
|
||||
'<span class="comment_comment">'.
|
||||
nl2br($entity->getComment()).
|
||||
'</span>';
|
||||
|
||||
if ($entity->getDate() instanceof \DateTime) {
|
||||
$str .= '<span class="comment_date">'.
|
||||
$entity->getDate()->format('d/m/Y H:i');
|
||||
'</span>';
|
||||
}
|
||||
|
||||
if (strlen($username) > 0) {
|
||||
$str .= '<span class="comment_user">'.
|
||||
$username.
|
||||
'</span>';
|
||||
}
|
||||
|
||||
$str .= $this->getDefaultClosingBox();
|
||||
|
||||
return $str;
|
||||
|
||||
return $this->engine
|
||||
->render(
|
||||
'@ChillMain/Entity/CommentEmbeddable.html.twig',
|
||||
[
|
||||
'opening_box' => $this->getDefaultOpeningBox('comment-embeddable'),
|
||||
'closing_box' => $this->getDefaultClosingBox(),
|
||||
'user' => $user ?? NULL,
|
||||
'comment' => $entity,
|
||||
'options' => $options
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS,
|
||||
* <http://www.champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\MainBundle\Tests\Templating;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Chill\MainBundle\Templating\ChillMarkdownRenderExtension;
|
||||
|
||||
/**
|
||||
* Test the service ChillMarkdownRenderExtension
|
||||
*
|
||||
* @internal we do not want to test the markdown transformation. We just want to
|
||||
* test that the markdown is correctly transformed into html, and that the html
|
||||
* is safe.
|
||||
*/
|
||||
class ChillMarkdownRenderExtensionTest extends TestCase
|
||||
{
|
||||
|
||||
private const SIMPLE_MARKDOWN = <<<MD
|
||||
# test
|
||||
|
||||
Text.
|
||||
MD;
|
||||
|
||||
private const SIMPLE_HTML = <<<HTML
|
||||
<h1>test</h1>
|
||||
<p>Text.</p>
|
||||
HTML;
|
||||
|
||||
private const UNAUTHORIZED_MARKDOWN = <<<MD
|
||||
<script>alert("ok");</script>
|
||||
MD;
|
||||
|
||||
private const UNAUTHORIZED_HTML = <<<HTML
|
||||
<p><script>alert("ok");</script></p>
|
||||
HTML;
|
||||
|
||||
/**
|
||||
* Test that the markdown input is transformed into html
|
||||
*/
|
||||
public function testRendering()
|
||||
{
|
||||
$extension = new ChillMarkdownRenderExtension();
|
||||
|
||||
$this->assertEquals(self::SIMPLE_HTML,
|
||||
$extension->renderMarkdownToHtml(self::SIMPLE_MARKDOWN));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the output of the markdown content is sanitized
|
||||
*/
|
||||
public function testSecurity()
|
||||
{
|
||||
$extension = new ChillMarkdownRenderExtension();
|
||||
|
||||
$this->assertEquals(self::UNAUTHORIZED_HTML,
|
||||
$extension->renderMarkdownToHtml(self::UNAUTHORIZED_MARKDOWN));
|
||||
}
|
||||
}
|
@@ -7,9 +7,8 @@ buildCKEditor = function(encore)
|
||||
.addPlugin( new CKEditorWebpackPlugin( {
|
||||
language: 'fr',
|
||||
addMainLanguageTranslationsToAllAssets: true,
|
||||
verbose: true,
|
||||
strict: true,
|
||||
//additionalLanguages: ['en', 'nl', 'es'],
|
||||
verbose: !encore.isProduction(),
|
||||
strict: true
|
||||
} ) )
|
||||
|
||||
// Use raw-loader for CKEditor 5 SVG files.
|
||||
@@ -35,7 +34,7 @@ buildCKEditor = function(encore)
|
||||
} )
|
||||
} )
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
// Compile and loads all assets from the Chill Main Bundle
|
||||
module.exports = function(encore, entries)
|
||||
@@ -61,8 +60,8 @@ module.exports = function(encore, entries)
|
||||
encore.addEntry('bootstrap', __dirname + '/Resources/public/modules/bootstrap/index.js');
|
||||
|
||||
// CKEditor5
|
||||
//buildCKEditor(encore);
|
||||
//encore.addEntry('ckeditor5', __dirname + '/Resources/public/modules/ckeditor5/index.js');
|
||||
buildCKEditor(encore);
|
||||
encore.addEntry('ckeditor5', __dirname + '/Resources/public/modules/ckeditor5/index.js');
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
@@ -1,16 +1,16 @@
|
||||
services:
|
||||
|
||||
|
||||
Chill\MainBundle\Controller\:
|
||||
autowire: true
|
||||
resource: '../../Controller'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
|
||||
Chill\MainBundle\Controller\PasswordController:
|
||||
autowire: true
|
||||
arguments:
|
||||
$chillLogger: '@monolog.logger.chill'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
|
||||
Chill\MainBundle\Controller\SearchController:
|
||||
arguments:
|
||||
$searchProvider: '@chill_main.search_provider'
|
||||
@@ -24,9 +24,11 @@ services:
|
||||
$roleProvider: '@chill.main.role_provider'
|
||||
$roleHierarchy: '@security.role_hierarchy'
|
||||
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
|
||||
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
Chill\MainBundle\Controller\UserController:
|
||||
arguments:
|
||||
$logger: '@Psr\Log\LoggerInterface'
|
||||
tags: ['controller.service_arguments']
|
||||
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
|
||||
tags: ['controller.service_arguments']
|
||||
|
@@ -138,10 +138,6 @@ services:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\MainBundle\Form\Extension\CKEditorExtension:
|
||||
tags:
|
||||
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\TextareaType }
|
||||
|
||||
chill.main.form.type.comment:
|
||||
class: Chill\MainBundle\Form\Type\CommentType
|
||||
arguments:
|
||||
|
@@ -34,5 +34,10 @@ services:
|
||||
Chill\MainBundle\Templating\Entity\CommentRender:
|
||||
arguments:
|
||||
- '@chill.main.user_repository'
|
||||
- '@Symfony\Component\Templating\EngineInterface'
|
||||
tags:
|
||||
- { name: 'chill.render_entity' }
|
||||
|
||||
Chill\MainBundle\Templating\ChillMarkdownRenderExtension:
|
||||
tags:
|
||||
- { name: twig.extension }
|
||||
|
@@ -35,6 +35,9 @@ This form contains errors: Ce formulaire contient des erreurs
|
||||
Choose an user: Choisir un utilisateur
|
||||
'You are going to leave a page with unsubmitted data. Are you sure you want to leave ?': "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
|
||||
No value: Aucune information
|
||||
Last updated by: Dernière mise à jour par
|
||||
Last updated on: Dernière mise à jour le
|
||||
on: le
|
||||
|
||||
Edit: Modifier
|
||||
Update: Mettre à jour
|
||||
|
Reference in New Issue
Block a user